summary refs log tree commit diff
path: root/drivers/gpu/drm/nouveau
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/gpu/drm/nouveau')
-rw-r--r--drivers/gpu/drm/nouveau/Kbuild1
-rw-r--r--drivers/gpu/drm/nouveau/dispnv04/disp.h16
-rw-r--r--drivers/gpu/drm/nouveau/include/nvif/cl5070.h39
-rw-r--r--drivers/gpu/drm/nouveau/include/nvkm/engine/disp.h10
-rw-r--r--drivers/gpu/drm/nouveau/include/nvkm/subdev/bios/init.h27
-rw-r--r--drivers/gpu/drm/nouveau/include/nvkm/subdev/timer.h1
-rw-r--r--drivers/gpu/drm/nouveau/nouveau_bios.c3
-rw-r--r--drivers/gpu/drm/nouveau/nouveau_connector.c10
-rw-r--r--drivers/gpu/drm/nouveau/nouveau_display.c39
-rw-r--r--drivers/gpu/drm/nouveau/nouveau_display.h8
-rw-r--r--drivers/gpu/drm/nouveau/nouveau_drm.c17
-rw-r--r--drivers/gpu/drm/nouveau/nouveau_drv.h2
-rw-r--r--drivers/gpu/drm/nouveau/nouveau_encoder.h1
-rw-r--r--drivers/gpu/drm/nouveau/nouveau_hwmon.c983
-rw-r--r--drivers/gpu/drm/nouveau/nouveau_ttm.c2
-rw-r--r--drivers/gpu/drm/nouveau/nouveau_vga.c4
-rw-r--r--drivers/gpu/drm/nouveau/nv50_display.c251
-rw-r--r--drivers/gpu/drm/nouveau/nvkm/engine/device/base.c6
-rw-r--r--drivers/gpu/drm/nouveau/nvkm/engine/device/tegra.c13
-rw-r--r--drivers/gpu/drm/nouveau/nvkm/engine/disp/Kbuild24
-rw-r--r--drivers/gpu/drm/nouveau/nvkm/engine/disp/base.c243
-rw-r--r--drivers/gpu/drm/nouveau/nvkm/engine/disp/basenv50.c3
-rw-r--r--drivers/gpu/drm/nouveau/nvkm/engine/disp/channv50.c1
-rw-r--r--drivers/gpu/drm/nouveau/nvkm/engine/disp/conn.c24
-rw-r--r--drivers/gpu/drm/nouveau/nvkm/engine/disp/conn.h14
-rw-r--r--drivers/gpu/drm/nouveau/nvkm/engine/disp/cursnv50.c3
-rw-r--r--drivers/gpu/drm/nouveau/nvkm/engine/disp/dacgf119.c65
-rw-r--r--drivers/gpu/drm/nouveau/nvkm/engine/disp/dacnv50.c140
-rw-r--r--drivers/gpu/drm/nouveau/nvkm/engine/disp/dp.c652
-rw-r--r--drivers/gpu/drm/nouveau/nvkm/engine/disp/dp.h (renamed from drivers/gpu/drm/nouveau/nvkm/engine/disp/dport.h)38
-rw-r--r--drivers/gpu/drm/nouveau/nvkm/engine/disp/dport.c401
-rw-r--r--drivers/gpu/drm/nouveau/nvkm/engine/disp/g84.c24
-rw-r--r--drivers/gpu/drm/nouveau/nvkm/engine/disp/g94.c25
-rw-r--r--drivers/gpu/drm/nouveau/nvkm/engine/disp/gf119.c422
-rw-r--r--drivers/gpu/drm/nouveau/nvkm/engine/disp/gk104.c21
-rw-r--r--drivers/gpu/drm/nouveau/nvkm/engine/disp/gk110.c21
-rw-r--r--drivers/gpu/drm/nouveau/nvkm/engine/disp/gm107.c21
-rw-r--r--drivers/gpu/drm/nouveau/nvkm/engine/disp/gm200.c22
-rw-r--r--drivers/gpu/drm/nouveau/nvkm/engine/disp/gp100.c21
-rw-r--r--drivers/gpu/drm/nouveau/nvkm/engine/disp/gp102.c21
-rw-r--r--drivers/gpu/drm/nouveau/nvkm/engine/disp/gt200.c24
-rw-r--r--drivers/gpu/drm/nouveau/nvkm/engine/disp/gt215.c26
-rw-r--r--drivers/gpu/drm/nouveau/nvkm/engine/disp/hdagf119.c75
-rw-r--r--drivers/gpu/drm/nouveau/nvkm/engine/disp/hdagt215.c74
-rw-r--r--drivers/gpu/drm/nouveau/nvkm/engine/disp/hdmi.c66
-rw-r--r--drivers/gpu/drm/nouveau/nvkm/engine/disp/hdmi.h15
-rw-r--r--drivers/gpu/drm/nouveau/nvkm/engine/disp/hdmig84.c73
-rw-r--r--drivers/gpu/drm/nouveau/nvkm/engine/disp/hdmigf119.c74
-rw-r--r--drivers/gpu/drm/nouveau/nvkm/engine/disp/hdmigk104.c72
-rw-r--r--drivers/gpu/drm/nouveau/nvkm/engine/disp/hdmigt215.c74
-rw-r--r--drivers/gpu/drm/nouveau/nvkm/engine/disp/head.c105
-rw-r--r--drivers/gpu/drm/nouveau/nvkm/engine/disp/head.h56
-rw-r--r--drivers/gpu/drm/nouveau/nvkm/engine/disp/headgf119.c96
-rw-r--r--drivers/gpu/drm/nouveau/nvkm/engine/disp/headnv04.c74
-rw-r--r--drivers/gpu/drm/nouveau/nvkm/engine/disp/headnv50.c92
-rw-r--r--drivers/gpu/drm/nouveau/nvkm/engine/disp/ior.c72
-rw-r--r--drivers/gpu/drm/nouveau/nvkm/engine/disp/ior.h169
-rw-r--r--drivers/gpu/drm/nouveau/nvkm/engine/disp/mcp77.c43
-rw-r--r--drivers/gpu/drm/nouveau/nvkm/engine/disp/mcp89.c43
-rw-r--r--drivers/gpu/drm/nouveau/nvkm/engine/disp/nv04.c31
-rw-r--r--drivers/gpu/drm/nouveau/nvkm/engine/disp/nv50.c991
-rw-r--r--drivers/gpu/drm/nouveau/nvkm/engine/disp/nv50.h76
-rw-r--r--drivers/gpu/drm/nouveau/nvkm/engine/disp/oimmnv50.c3
-rw-r--r--drivers/gpu/drm/nouveau/nvkm/engine/disp/outp.c214
-rw-r--r--drivers/gpu/drm/nouveau/nvkm/engine/disp/outp.h55
-rw-r--r--drivers/gpu/drm/nouveau/nvkm/engine/disp/outpdp.c282
-rw-r--r--drivers/gpu/drm/nouveau/nvkm/engine/disp/outpdp.h76
-rw-r--r--drivers/gpu/drm/nouveau/nvkm/engine/disp/ovlynv50.c3
-rw-r--r--drivers/gpu/drm/nouveau/nvkm/engine/disp/piornv50.c163
-rw-r--r--drivers/gpu/drm/nouveau/nvkm/engine/disp/priv.h28
-rw-r--r--drivers/gpu/drm/nouveau/nvkm/engine/disp/rootgf119.c60
-rw-r--r--drivers/gpu/drm/nouveau/nvkm/engine/disp/rootnv04.c54
-rw-r--r--drivers/gpu/drm/nouveau/nvkm/engine/disp/rootnv50.c259
-rw-r--r--drivers/gpu/drm/nouveau/nvkm/engine/disp/sorg84.c38
-rw-r--r--drivers/gpu/drm/nouveau/nvkm/engine/disp/sorg94.c290
-rw-r--r--drivers/gpu/drm/nouveau/nvkm/engine/disp/sorgf119.c203
-rw-r--r--drivers/gpu/drm/nouveau/nvkm/engine/disp/sorgk104.c53
-rw-r--r--drivers/gpu/drm/nouveau/nvkm/engine/disp/sorgm107.c49
-rw-r--r--drivers/gpu/drm/nouveau/nvkm/engine/disp/sorgm200.c163
-rw-r--r--drivers/gpu/drm/nouveau/nvkm/engine/disp/sorgt215.c69
-rw-r--r--drivers/gpu/drm/nouveau/nvkm/engine/disp/sormcp77.c48
-rw-r--r--drivers/gpu/drm/nouveau/nvkm/engine/disp/sormcp89.c53
-rw-r--r--drivers/gpu/drm/nouveau/nvkm/engine/disp/sornv50.c100
-rw-r--r--drivers/gpu/drm/nouveau/nvkm/subdev/bios/iccsense.c5
-rw-r--r--drivers/gpu/drm/nouveau/nvkm/subdev/bios/init.c227
-rw-r--r--drivers/gpu/drm/nouveau/nvkm/subdev/bios/volt.c6
-rw-r--r--drivers/gpu/drm/nouveau/nvkm/subdev/devinit/nv04.c2
-rw-r--r--drivers/gpu/drm/nouveau/nvkm/subdev/devinit/nv50.c15
-rw-r--r--drivers/gpu/drm/nouveau/nvkm/subdev/fb/ramgk104.c7
-rw-r--r--drivers/gpu/drm/nouveau/nvkm/subdev/fb/ramgp100.c7
-rw-r--r--drivers/gpu/drm/nouveau/nvkm/subdev/fb/ramnv40.c12
-rw-r--r--drivers/gpu/drm/nouveau/nvkm/subdev/pmu/gk20a.c2
-rw-r--r--drivers/gpu/drm/nouveau/nvkm/subdev/therm/base.c4
-rw-r--r--drivers/gpu/drm/nouveau/nvkm/subdev/therm/fan.c2
-rw-r--r--drivers/gpu/drm/nouveau/nvkm/subdev/therm/temp.c2
-rw-r--r--drivers/gpu/drm/nouveau/nvkm/subdev/timer/base.c9
96 files changed, 4553 insertions, 4070 deletions
diff --git a/drivers/gpu/drm/nouveau/Kbuild b/drivers/gpu/drm/nouveau/Kbuild
index fde6e3656636..2e9ce53ae3a8 100644
--- a/drivers/gpu/drm/nouveau/Kbuild
+++ b/drivers/gpu/drm/nouveau/Kbuild
@@ -1,4 +1,3 @@
-ccflags-y := -Iinclude/drm
 ccflags-y += -I$(src)/include
 ccflags-y += -I$(src)/include/nvkm
 ccflags-y += -I$(src)/nvkm
diff --git a/drivers/gpu/drm/nouveau/dispnv04/disp.h b/drivers/gpu/drm/nouveau/dispnv04/disp.h
index bea4543554ba..74a8795c2c2b 100644
--- a/drivers/gpu/drm/nouveau/dispnv04/disp.h
+++ b/drivers/gpu/drm/nouveau/dispnv04/disp.h
@@ -169,18 +169,10 @@ static inline void
 nouveau_bios_run_init_table(struct drm_device *dev, u16 table,
 			    struct dcb_output *outp, int crtc)
 {
-	struct nouveau_drm *drm = nouveau_drm(dev);
-	struct nvkm_bios *bios = nvxx_bios(&drm->client.device);
-	struct nvbios_init init = {
-		.subdev = &bios->subdev,
-		.bios = bios,
-		.offset = table,
-		.outp = outp,
-		.crtc = crtc,
-		.execute = 1,
-	};
-
-	nvbios_exec(&init);
+	nvbios_init(&nvxx_bios(&nouveau_drm(dev)->client.device)->subdev, table,
+		init.outp = outp;
+		init.head = crtc;
+	);
 }
 
 #endif
diff --git a/drivers/gpu/drm/nouveau/include/nvif/cl5070.h b/drivers/gpu/drm/nouveau/include/nvif/cl5070.h
index ae49dfd1f97b..542d95145a67 100644
--- a/drivers/gpu/drm/nouveau/include/nvif/cl5070.h
+++ b/drivers/gpu/drm/nouveau/include/nvif/cl5070.h
@@ -27,29 +27,25 @@ struct nv50_disp_scanoutpos_v0 {
 
 struct nv50_disp_mthd_v1 {
 	__u8  version;
-#define NV50_DISP_MTHD_V1_DAC_PWR                                          0x10
+#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_SOR_PWR                                          0x20
 #define NV50_DISP_MTHD_V1_SOR_HDA_ELD                                      0x21
 #define NV50_DISP_MTHD_V1_SOR_HDMI_PWR                                     0x22
 #define NV50_DISP_MTHD_V1_SOR_LVDS_SCRIPT                                  0x23
-#define NV50_DISP_MTHD_V1_SOR_DP_PWR                                       0x24
 #define NV50_DISP_MTHD_V1_SOR_DP_MST_LINK                                  0x25
 #define NV50_DISP_MTHD_V1_SOR_DP_MST_VCPI                                  0x26
-#define NV50_DISP_MTHD_V1_PIOR_PWR                                         0x30
 	__u8  method;
 	__u16 hasht;
 	__u16 hashm;
 	__u8  pad06[2];
 };
 
-struct nv50_disp_dac_pwr_v0 {
+struct nv50_disp_acquire_v0 {
 	__u8  version;
-	__u8  state;
-	__u8  data;
-	__u8  vsync;
-	__u8  hsync;
-	__u8  pad05[3];
+	__u8  or;
+	__u8  link;
+	__u8  pad03[5];
 };
 
 struct nv50_disp_dac_load_v0 {
@@ -59,12 +55,6 @@ struct nv50_disp_dac_load_v0 {
 	__u32 data;
 };
 
-struct nv50_disp_sor_pwr_v0 {
-	__u8  version;
-	__u8  state;
-	__u8  pad02[6];
-};
-
 struct nv50_disp_sor_hda_eld_v0 {
 	__u8  version;
 	__u8  pad01[7];
@@ -76,7 +66,9 @@ struct nv50_disp_sor_hdmi_pwr_v0 {
 	__u8  state;
 	__u8  max_ac_packet;
 	__u8  rekey;
-	__u8  pad04[4];
+	__u8  avi_infoframe_length;
+	__u8  vendor_infoframe_length;
+	__u8  pad06[2];
 };
 
 struct nv50_disp_sor_lvds_script_v0 {
@@ -86,12 +78,6 @@ struct nv50_disp_sor_lvds_script_v0 {
 	__u8  pad04[4];
 };
 
-struct nv50_disp_sor_dp_pwr_v0 {
-	__u8  version;
-	__u8  state;
-	__u8  pad02[6];
-};
-
 struct nv50_disp_sor_dp_mst_link_v0 {
 	__u8  version;
 	__u8  state;
@@ -106,11 +92,4 @@ struct nv50_disp_sor_dp_mst_vcpi_v0 {
 	__u16 pbn;
 	__u16 aligned_pbn;
 };
-
-struct nv50_disp_pior_pwr_v0 {
-	__u8  version;
-	__u8  state;
-	__u8  type;
-	__u8  pad03[5];
-};
 #endif
diff --git a/drivers/gpu/drm/nouveau/include/nvkm/engine/disp.h b/drivers/gpu/drm/nouveau/include/nvkm/engine/disp.h
index 970ae753968a..05f9c13ab8c3 100644
--- a/drivers/gpu/drm/nouveau/include/nvkm/engine/disp.h
+++ b/drivers/gpu/drm/nouveau/include/nvkm/engine/disp.h
@@ -8,17 +8,15 @@ struct nvkm_disp {
 	const struct nvkm_disp_func *func;
 	struct nvkm_engine engine;
 
-	struct nvkm_oproxy *client;
-
+	struct list_head head;
+	struct list_head ior;
 	struct list_head outp;
 	struct list_head conn;
 
 	struct nvkm_event hpd;
 	struct nvkm_event vblank;
 
-	struct {
-		int nr;
-	} head;
+	struct nvkm_oproxy *client;
 };
 
 int nv04_disp_new(struct nvkm_device *, int, struct nvkm_disp **);
@@ -26,7 +24,9 @@ int nv50_disp_new(struct nvkm_device *, int, struct nvkm_disp **);
 int g84_disp_new(struct nvkm_device *, int, struct nvkm_disp **);
 int gt200_disp_new(struct nvkm_device *, int, struct nvkm_disp **);
 int g94_disp_new(struct nvkm_device *, int, struct nvkm_disp **);
+int mcp77_disp_new(struct nvkm_device *, int, struct nvkm_disp **);
 int gt215_disp_new(struct nvkm_device *, int, struct nvkm_disp **);
+int mcp89_disp_new(struct nvkm_device *, int, struct nvkm_disp **);
 int gf119_disp_new(struct nvkm_device *, int, struct nvkm_disp **);
 int gk104_disp_new(struct nvkm_device *, int, struct nvkm_disp **);
 int gk110_disp_new(struct nvkm_device *, int, struct nvkm_disp **);
diff --git a/drivers/gpu/drm/nouveau/include/nvkm/subdev/bios/init.h b/drivers/gpu/drm/nouveau/include/nvkm/subdev/bios/init.h
index 4dc1c8af840c..06ab48052128 100644
--- a/drivers/gpu/drm/nouveau/include/nvkm/subdev/bios/init.h
+++ b/drivers/gpu/drm/nouveau/include/nvkm/subdev/bios/init.h
@@ -3,19 +3,34 @@
 
 struct nvbios_init {
 	struct nvkm_subdev *subdev;
-	struct nvkm_bios *bios;
-	u16 offset;
+	u32 offset;
+
 	struct dcb_output *outp;
-	int crtc;
+	int or;
+	int link;
+	int head;
 
 	/* internal state used during parsing */
 	u8 execute;
 	u32 nested;
-	u16 repeat;
-	u16 repend;
+	u32 repeat;
+	u32 repend;
 	u32 ramcfg;
 };
 
+#define nvbios_init(s,o,ARGS...) ({                                            \
+	struct nvbios_init init = {                                            \
+		.subdev = (s),                                                 \
+		.offset = (o),                                                 \
+		.or = -1,                                                      \
+		.link = 0,                                                     \
+		.head = -1,                                                    \
+		.execute = 1,                                                  \
+	};                                                                     \
+	ARGS                                                                   \
+	nvbios_exec(&init);                                                    \
+})
 int nvbios_exec(struct nvbios_init *);
-int nvbios_init(struct nvkm_subdev *, bool execute);
+
+int nvbios_post(struct nvkm_subdev *, bool execute);
 #endif
diff --git a/drivers/gpu/drm/nouveau/include/nvkm/subdev/timer.h b/drivers/gpu/drm/nouveau/include/nvkm/subdev/timer.h
index 820a4805916f..ff0709652f80 100644
--- a/drivers/gpu/drm/nouveau/include/nvkm/subdev/timer.h
+++ b/drivers/gpu/drm/nouveau/include/nvkm/subdev/timer.h
@@ -26,7 +26,6 @@ struct nvkm_timer {
 
 u64 nvkm_timer_read(struct nvkm_timer *);
 void nvkm_timer_alarm(struct nvkm_timer *, u32 nsec, struct nvkm_alarm *);
-void nvkm_timer_alarm_cancel(struct nvkm_timer *, struct nvkm_alarm *);
 
 /* Delay based on GPU time (ie. PTIMER).
  *
diff --git a/drivers/gpu/drm/nouveau/nouveau_bios.c b/drivers/gpu/drm/nouveau/nouveau_bios.c
index 9a0772ad495a..b998c33af18a 100644
--- a/drivers/gpu/drm/nouveau/nouveau_bios.c
+++ b/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)
 		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;
 	return true;
 }
diff --git a/drivers/gpu/drm/nouveau/nouveau_connector.c b/drivers/gpu/drm/nouveau/nouveau_connector.c
index f802bcd94457..147b22163f9f 100644
--- a/drivers/gpu/drm/nouveau/nouveau_connector.c
+++ b/drivers/gpu/drm/nouveau/nouveau_connector.c
@@ -1045,6 +1045,9 @@ nouveau_connector_mode_valid(struct drm_connector *connector,
 		return MODE_BAD;
 	}
 
+	if ((mode->flags & DRM_MODE_FLAG_3D_MASK) == DRM_MODE_FLAG_3D_FRAME_PACKING)
+		clock *= 2;
+
 	if (clock < min_clock)
 		return MODE_CLOCK_LOW;
 
@@ -1321,6 +1324,13 @@ nouveau_connector_create(struct drm_device *dev, int index)
 		break;
 	}
 
+	/* HDMI 3D support */
+	if ((disp->disp.oclass >= G82_DISP)
+	    && ((type == DRM_MODE_CONNECTOR_DisplayPort)
+		|| (type == DRM_MODE_CONNECTOR_eDP)
+		|| (type == DRM_MODE_CONNECTOR_HDMIA)))
+		connector->stereo_allowed = true;
+
 	/* defaults, will get overridden in detect() */
 	connector->interlace_allowed = false;
 	connector->doublescan_allowed = false;
diff --git a/drivers/gpu/drm/nouveau/nouveau_display.c b/drivers/gpu/drm/nouveau/nouveau_display.c
index 549763f5e17d..8d1df5678eaa 100644
--- a/drivers/gpu/drm/nouveau/nouveau_display.c
+++ b/drivers/gpu/drm/nouveau/nouveau_display.c
@@ -98,7 +98,7 @@ calc(int blanks, int blanke, int total, int line)
 	return line;
 }
 
-static int
+static bool
 nouveau_display_scanoutpos_head(struct drm_crtc *crtc, int *vpos, int *hpos,
 				ktime_t *stime, ktime_t *etime)
 {
@@ -111,16 +111,16 @@ nouveau_display_scanoutpos_head(struct drm_crtc *crtc, int *vpos, int *hpos,
 	};
 	struct nouveau_display *disp = nouveau_display(crtc->dev);
 	struct drm_vblank_crtc *vblank = &crtc->dev->vblank[drm_crtc_index(crtc)];
-	int ret, retry = 20;
+	int retry = 20;
+	bool ret = false;
 
 	do {
 		ret = nvif_mthd(&disp->disp, 0, &args, sizeof(args));
 		if (ret != 0)
-			return 0;
+			return false;
 
 		if (args.scan.vline) {
-			ret |= DRM_SCANOUTPOS_ACCURATE;
-			ret |= DRM_SCANOUTPOS_VALID;
+			ret = true;
 			break;
 		}
 
@@ -133,14 +133,12 @@ nouveau_display_scanoutpos_head(struct drm_crtc *crtc, int *vpos, int *hpos,
 	if (stime) *stime = ns_to_ktime(args.scan.time[0]);
 	if (etime) *etime = ns_to_ktime(args.scan.time[1]);
 
-	if (*vpos < 0)
-		ret |= DRM_SCANOUTPOS_IN_VBLANK;
 	return ret;
 }
 
-int
+bool
 nouveau_display_scanoutpos(struct drm_device *dev, unsigned int pipe,
-			   unsigned int flags, int *vpos, int *hpos,
+			   bool in_vblank_irq, int *vpos, int *hpos,
 			   ktime_t *stime, ktime_t *etime,
 			   const struct drm_display_mode *mode)
 {
@@ -153,28 +151,7 @@ nouveau_display_scanoutpos(struct drm_device *dev, unsigned int pipe,
 		}
 	}
 
-	return 0;
-}
-
-int
-nouveau_display_vblstamp(struct drm_device *dev, unsigned int pipe,
-			 int *max_error, struct timeval *time, unsigned flags)
-{
-	struct drm_crtc *crtc;
-
-	list_for_each_entry(crtc, &dev->mode_config.crtc_list, head) {
-		if (nouveau_crtc(crtc)->index == pipe) {
-			struct drm_display_mode *mode;
-			if (drm_drv_uses_atomic_modeset(dev))
-				mode = &crtc->state->adjusted_mode;
-			else
-				mode = &crtc->hwmode;
-			return drm_calc_vbltimestamp_from_scanoutpos(dev,
-					pipe, max_error, time, flags, mode);
-		}
-	}
-
-	return -EINVAL;
+	return false;
 }
 
 static void
diff --git a/drivers/gpu/drm/nouveau/nouveau_display.h b/drivers/gpu/drm/nouveau/nouveau_display.h
index e1d772d39488..201aec2ea5b8 100644
--- a/drivers/gpu/drm/nouveau/nouveau_display.h
+++ b/drivers/gpu/drm/nouveau/nouveau_display.h
@@ -68,11 +68,9 @@ int  nouveau_display_suspend(struct drm_device *dev, bool runtime);
 void nouveau_display_resume(struct drm_device *dev, bool runtime);
 int  nouveau_display_vblank_enable(struct drm_device *, unsigned int);
 void nouveau_display_vblank_disable(struct drm_device *, unsigned int);
-int  nouveau_display_scanoutpos(struct drm_device *, unsigned int,
-				unsigned int, int *, int *, ktime_t *,
-				ktime_t *, const struct drm_display_mode *);
-int  nouveau_display_vblstamp(struct drm_device *, unsigned int, int *,
-			      struct timeval *, unsigned);
+bool  nouveau_display_scanoutpos(struct drm_device *, unsigned int,
+				 bool, int *, int *, ktime_t *,
+				 ktime_t *, const struct drm_display_mode *);
 
 int  nouveau_crtc_page_flip(struct drm_crtc *crtc, struct drm_framebuffer *fb,
 			    struct drm_pending_vblank_event *event,
diff --git a/drivers/gpu/drm/nouveau/nouveau_drm.c b/drivers/gpu/drm/nouveau/nouveau_drm.c
index 15a13d09d431..90757af9bc73 100644
--- a/drivers/gpu/drm/nouveau/nouveau_drm.c
+++ b/drivers/gpu/drm/nouveau/nouveau_drm.c
@@ -29,8 +29,8 @@
 #include <linux/pm_runtime.h>
 #include <linux/vga_switcheroo.h>
 
-#include "drmP.h"
-#include "drm_crtc_helper.h"
+#include <drm/drmP.h>
+#include <drm/drm_crtc_helper.h>
 
 #include <core/gpuobj.h>
 #include <core/option.h>
@@ -727,7 +727,7 @@ nouveau_pmops_thaw(struct device *dev)
 }
 
 bool
-nouveau_pmops_runtime()
+nouveau_pmops_runtime(void)
 {
 	if (nouveau_runtime_pm == -1)
 		return nouveau_is_optimus() || nouveau_is_v1_dsm();
@@ -877,7 +877,7 @@ done:
 }
 
 static void
-nouveau_drm_preclose(struct drm_device *dev, struct drm_file *fpriv)
+nouveau_drm_postclose(struct drm_device *dev, struct drm_file *fpriv)
 {
 	struct nouveau_cli *cli = nouveau_cli(fpriv);
 	struct nouveau_drm *drm = nouveau_drm(dev);
@@ -893,12 +893,6 @@ nouveau_drm_preclose(struct drm_device *dev, struct drm_file *fpriv)
 	list_del(&cli->head);
 	mutex_unlock(&drm->client.mutex);
 
-}
-
-static void
-nouveau_drm_postclose(struct drm_device *dev, struct drm_file *fpriv)
-{
-	struct nouveau_cli *cli = nouveau_cli(fpriv);
 	nouveau_cli_fini(cli);
 	kfree(cli);
 	pm_runtime_mark_last_busy(dev->dev);
@@ -970,7 +964,6 @@ driver_stub = {
 	.load = nouveau_drm_load,
 	.unload = nouveau_drm_unload,
 	.open = nouveau_drm_open,
-	.preclose = nouveau_drm_preclose,
 	.postclose = nouveau_drm_postclose,
 	.lastclose = nouveau_vga_lastclose,
 
@@ -981,7 +974,7 @@ driver_stub = {
 	.enable_vblank = nouveau_display_vblank_enable,
 	.disable_vblank = nouveau_display_vblank_disable,
 	.get_scanout_position = nouveau_display_scanoutpos,
-	.get_vblank_timestamp = nouveau_display_vblstamp,
+	.get_vblank_timestamp = drm_calc_vbltimestamp_from_scanoutpos,
 
 	.ioctls = nouveau_ioctls,
 	.num_ioctls = ARRAY_SIZE(nouveau_ioctls),
diff --git a/drivers/gpu/drm/nouveau/nouveau_drv.h b/drivers/gpu/drm/nouveau/nouveau_drv.h
index a11b6aaed325..822fe1d4d35e 100644
--- a/drivers/gpu/drm/nouveau/nouveau_drv.h
+++ b/drivers/gpu/drm/nouveau/nouveau_drv.h
@@ -43,7 +43,7 @@
 #include <nvif/device.h>
 #include <nvif/ioctl.h>
 
-#include <drmP.h>
+#include <drm/drmP.h>
 
 #include <drm/ttm/ttm_bo_api.h>
 #include <drm/ttm/ttm_bo_driver.h>
diff --git a/drivers/gpu/drm/nouveau/nouveau_encoder.h b/drivers/gpu/drm/nouveau/nouveau_encoder.h
index 198e5f27682f..e28d966946a1 100644
--- a/drivers/gpu/drm/nouveau/nouveau_encoder.h
+++ b/drivers/gpu/drm/nouveau/nouveau_encoder.h
@@ -42,6 +42,7 @@ struct nouveau_encoder {
 
 	struct dcb_output *dcb;
 	int or;
+	int link;
 
 	struct i2c_adapter *i2c;
 	struct nvkm_i2c_aux *aux;
diff --git a/drivers/gpu/drm/nouveau/nouveau_hwmon.c b/drivers/gpu/drm/nouveau/nouveau_hwmon.c
index 23b1670c1c2f..7c965648df80 100644
--- a/drivers/gpu/drm/nouveau/nouveau_hwmon.c
+++ b/drivers/gpu/drm/nouveau/nouveau_hwmon.c
@@ -38,21 +38,6 @@
 #include <nvkm/subdev/volt.h>
 
 #if defined(CONFIG_HWMON) || (defined(MODULE) && defined(CONFIG_HWMON_MODULE))
-static ssize_t
-nouveau_hwmon_show_temp(struct device *d, struct device_attribute *a, char *buf)
-{
-	struct drm_device *dev = dev_get_drvdata(d);
-	struct nouveau_drm *drm = nouveau_drm(dev);
-	struct nvkm_therm *therm = nvxx_therm(&drm->client.device);
-	int temp = nvkm_therm_temp_get(therm);
-
-	if (temp < 0)
-		return temp;
-
-	return snprintf(buf, PAGE_SIZE, "%d\n", temp * 1000);
-}
-static SENSOR_DEVICE_ATTR(temp1_input, S_IRUGO, nouveau_hwmon_show_temp,
-						  NULL, 0);
 
 static ssize_t
 nouveau_hwmon_show_temp1_auto_point1_pwm(struct device *d,
@@ -60,7 +45,7 @@ nouveau_hwmon_show_temp1_auto_point1_pwm(struct device *d,
 {
 	return snprintf(buf, PAGE_SIZE, "%d\n", 100);
 }
-static SENSOR_DEVICE_ATTR(temp1_auto_point1_pwm, S_IRUGO,
+static SENSOR_DEVICE_ATTR(temp1_auto_point1_pwm, 0444,
 			  nouveau_hwmon_show_temp1_auto_point1_pwm, NULL, 0);
 
 static ssize_t
@@ -92,7 +77,7 @@ nouveau_hwmon_set_temp1_auto_point1_temp(struct device *d,
 
 	return count;
 }
-static SENSOR_DEVICE_ATTR(temp1_auto_point1_temp, S_IRUGO | S_IWUSR,
+static SENSOR_DEVICE_ATTR(temp1_auto_point1_temp, 0644,
 			  nouveau_hwmon_temp1_auto_point1_temp,
 			  nouveau_hwmon_set_temp1_auto_point1_temp, 0);
 
@@ -125,572 +110,595 @@ nouveau_hwmon_set_temp1_auto_point1_temp_hyst(struct device *d,
 
 	return count;
 }
-static SENSOR_DEVICE_ATTR(temp1_auto_point1_temp_hyst, S_IRUGO | S_IWUSR,
+static SENSOR_DEVICE_ATTR(temp1_auto_point1_temp_hyst, 0644,
 			  nouveau_hwmon_temp1_auto_point1_temp_hyst,
 			  nouveau_hwmon_set_temp1_auto_point1_temp_hyst, 0);
 
 static ssize_t
-nouveau_hwmon_max_temp(struct device *d, struct device_attribute *a, char *buf)
-{
-	struct drm_device *dev = dev_get_drvdata(d);
-	struct nouveau_drm *drm = nouveau_drm(dev);
-	struct nvkm_therm *therm = nvxx_therm(&drm->client.device);
-
-	return snprintf(buf, PAGE_SIZE, "%d\n",
-	       therm->attr_get(therm, NVKM_THERM_ATTR_THRS_DOWN_CLK) * 1000);
-}
-static ssize_t
-nouveau_hwmon_set_max_temp(struct device *d, struct device_attribute *a,
-						const char *buf, size_t count)
+nouveau_hwmon_get_pwm1_max(struct device *d,
+			   struct device_attribute *a, char *buf)
 {
 	struct drm_device *dev = dev_get_drvdata(d);
 	struct nouveau_drm *drm = nouveau_drm(dev);
 	struct nvkm_therm *therm = nvxx_therm(&drm->client.device);
-	long value;
-
-	if (kstrtol(buf, 10, &value) == -EINVAL)
-		return count;
+	int ret;
 
-	therm->attr_set(therm, NVKM_THERM_ATTR_THRS_DOWN_CLK, value / 1000);
+	ret = therm->attr_get(therm, NVKM_THERM_ATTR_FAN_MAX_DUTY);
+	if (ret < 0)
+		return ret;
 
-	return count;
+	return sprintf(buf, "%i\n", ret);
 }
-static SENSOR_DEVICE_ATTR(temp1_max, S_IRUGO | S_IWUSR, nouveau_hwmon_max_temp,
-						  nouveau_hwmon_set_max_temp,
-						  0);
 
 static ssize_t
-nouveau_hwmon_max_temp_hyst(struct device *d, struct device_attribute *a,
-			    char *buf)
+nouveau_hwmon_get_pwm1_min(struct device *d,
+			   struct device_attribute *a, char *buf)
 {
 	struct drm_device *dev = dev_get_drvdata(d);
 	struct nouveau_drm *drm = nouveau_drm(dev);
 	struct nvkm_therm *therm = nvxx_therm(&drm->client.device);
+	int ret;
 
-	return snprintf(buf, PAGE_SIZE, "%d\n",
-	  therm->attr_get(therm, NVKM_THERM_ATTR_THRS_DOWN_CLK_HYST) * 1000);
+	ret = therm->attr_get(therm, NVKM_THERM_ATTR_FAN_MIN_DUTY);
+	if (ret < 0)
+		return ret;
+
+	return sprintf(buf, "%i\n", ret);
 }
+
 static ssize_t
-nouveau_hwmon_set_max_temp_hyst(struct device *d, struct device_attribute *a,
-						const char *buf, size_t count)
+nouveau_hwmon_set_pwm1_min(struct device *d, struct device_attribute *a,
+			   const char *buf, size_t count)
 {
 	struct drm_device *dev = dev_get_drvdata(d);
 	struct nouveau_drm *drm = nouveau_drm(dev);
 	struct nvkm_therm *therm = nvxx_therm(&drm->client.device);
 	long value;
+	int ret;
 
 	if (kstrtol(buf, 10, &value) == -EINVAL)
-		return count;
+		return -EINVAL;
 
-	therm->attr_set(therm, NVKM_THERM_ATTR_THRS_DOWN_CLK_HYST,
-			value / 1000);
+	ret = therm->attr_set(therm, NVKM_THERM_ATTR_FAN_MIN_DUTY, value);
+	if (ret < 0)
+		return ret;
 
 	return count;
 }
-static SENSOR_DEVICE_ATTR(temp1_max_hyst, S_IRUGO | S_IWUSR,
-			  nouveau_hwmon_max_temp_hyst,
-			  nouveau_hwmon_set_max_temp_hyst, 0);
-
-static ssize_t
-nouveau_hwmon_critical_temp(struct device *d, struct device_attribute *a,
-							char *buf)
-{
-	struct drm_device *dev = dev_get_drvdata(d);
-	struct nouveau_drm *drm = nouveau_drm(dev);
-	struct nvkm_therm *therm = nvxx_therm(&drm->client.device);
+static SENSOR_DEVICE_ATTR(pwm1_min, 0644,
+			  nouveau_hwmon_get_pwm1_min,
+			  nouveau_hwmon_set_pwm1_min, 0);
 
-	return snprintf(buf, PAGE_SIZE, "%d\n",
-	       therm->attr_get(therm, NVKM_THERM_ATTR_THRS_CRITICAL) * 1000);
-}
 static ssize_t
-nouveau_hwmon_set_critical_temp(struct device *d, struct device_attribute *a,
-							    const char *buf,
-								size_t count)
+nouveau_hwmon_set_pwm1_max(struct device *d, struct device_attribute *a,
+			   const char *buf, size_t count)
 {
 	struct drm_device *dev = dev_get_drvdata(d);
 	struct nouveau_drm *drm = nouveau_drm(dev);
 	struct nvkm_therm *therm = nvxx_therm(&drm->client.device);
 	long value;
+	int ret;
 
 	if (kstrtol(buf, 10, &value) == -EINVAL)
-		return count;
+		return -EINVAL;
 
-	therm->attr_set(therm, NVKM_THERM_ATTR_THRS_CRITICAL, value / 1000);
+	ret = therm->attr_set(therm, NVKM_THERM_ATTR_FAN_MAX_DUTY, value);
+	if (ret < 0)
+		return ret;
 
 	return count;
 }
-static SENSOR_DEVICE_ATTR(temp1_crit, S_IRUGO | S_IWUSR,
-						nouveau_hwmon_critical_temp,
-						nouveau_hwmon_set_critical_temp,
-						0);
+static SENSOR_DEVICE_ATTR(pwm1_max, 0644,
+			  nouveau_hwmon_get_pwm1_max,
+			  nouveau_hwmon_set_pwm1_max, 0);
 
-static ssize_t
-nouveau_hwmon_critical_temp_hyst(struct device *d, struct device_attribute *a,
-							char *buf)
-{
-	struct drm_device *dev = dev_get_drvdata(d);
-	struct nouveau_drm *drm = nouveau_drm(dev);
-	struct nvkm_therm *therm = nvxx_therm(&drm->client.device);
+static struct attribute *pwm_fan_sensor_attrs[] = {
+	&sensor_dev_attr_pwm1_min.dev_attr.attr,
+	&sensor_dev_attr_pwm1_max.dev_attr.attr,
+	NULL
+};
+static const struct attribute_group pwm_fan_sensor_group = {
+	.attrs = pwm_fan_sensor_attrs,
+};
 
-	return snprintf(buf, PAGE_SIZE, "%d\n",
-	  therm->attr_get(therm, NVKM_THERM_ATTR_THRS_CRITICAL_HYST) * 1000);
-}
-static ssize_t
-nouveau_hwmon_set_critical_temp_hyst(struct device *d,
-				     struct device_attribute *a,
-				     const char *buf,
-				     size_t count)
-{
-	struct drm_device *dev = dev_get_drvdata(d);
-	struct nouveau_drm *drm = nouveau_drm(dev);
-	struct nvkm_therm *therm = nvxx_therm(&drm->client.device);
-	long value;
+static struct attribute *temp1_auto_point_sensor_attrs[] = {
+	&sensor_dev_attr_temp1_auto_point1_pwm.dev_attr.attr,
+	&sensor_dev_attr_temp1_auto_point1_temp.dev_attr.attr,
+	&sensor_dev_attr_temp1_auto_point1_temp_hyst.dev_attr.attr,
+	NULL
+};
+static const struct attribute_group temp1_auto_point_sensor_group = {
+	.attrs = temp1_auto_point_sensor_attrs,
+};
 
-	if (kstrtol(buf, 10, &value) == -EINVAL)
-		return count;
+#define N_ATTR_GROUPS   3
 
-	therm->attr_set(therm, NVKM_THERM_ATTR_THRS_CRITICAL_HYST,
-			value / 1000);
+static const u32 nouveau_config_chip[] = {
+	HWMON_C_UPDATE_INTERVAL,
+	0
+};
 
-	return count;
-}
-static SENSOR_DEVICE_ATTR(temp1_crit_hyst, S_IRUGO | S_IWUSR,
-			  nouveau_hwmon_critical_temp_hyst,
-			  nouveau_hwmon_set_critical_temp_hyst, 0);
-static ssize_t
-nouveau_hwmon_emergency_temp(struct device *d, struct device_attribute *a,
-							char *buf)
-{
-	struct drm_device *dev = dev_get_drvdata(d);
-	struct nouveau_drm *drm = nouveau_drm(dev);
-	struct nvkm_therm *therm = nvxx_therm(&drm->client.device);
+static const u32 nouveau_config_in[] = {
+	HWMON_I_INPUT | HWMON_I_MIN | HWMON_I_MAX | HWMON_I_LABEL,
+	0
+};
 
-	return snprintf(buf, PAGE_SIZE, "%d\n",
-	       therm->attr_get(therm, NVKM_THERM_ATTR_THRS_SHUTDOWN) * 1000);
-}
-static ssize_t
-nouveau_hwmon_set_emergency_temp(struct device *d, struct device_attribute *a,
-							    const char *buf,
-								size_t count)
-{
-	struct drm_device *dev = dev_get_drvdata(d);
-	struct nouveau_drm *drm = nouveau_drm(dev);
-	struct nvkm_therm *therm = nvxx_therm(&drm->client.device);
-	long value;
+static const u32 nouveau_config_temp[] = {
+	HWMON_T_INPUT | HWMON_T_MAX | HWMON_T_MAX_HYST |
+	HWMON_T_CRIT | HWMON_T_CRIT_HYST | HWMON_T_EMERGENCY |
+	HWMON_T_EMERGENCY_HYST,
+	0
+};
 
-	if (kstrtol(buf, 10, &value) == -EINVAL)
-		return count;
+static const u32 nouveau_config_fan[] = {
+	HWMON_F_INPUT,
+	0
+};
 
-	therm->attr_set(therm, NVKM_THERM_ATTR_THRS_SHUTDOWN, value / 1000);
+static const u32 nouveau_config_pwm[] = {
+	HWMON_PWM_INPUT | HWMON_PWM_ENABLE,
+	0
+};
 
-	return count;
-}
-static SENSOR_DEVICE_ATTR(temp1_emergency, S_IRUGO | S_IWUSR,
-					nouveau_hwmon_emergency_temp,
-					nouveau_hwmon_set_emergency_temp,
-					0);
+static const u32 nouveau_config_power[] = {
+	HWMON_P_INPUT | HWMON_P_CAP_MAX | HWMON_P_CRIT,
+	0
+};
 
-static ssize_t
-nouveau_hwmon_emergency_temp_hyst(struct device *d, struct device_attribute *a,
-							char *buf)
-{
-	struct drm_device *dev = dev_get_drvdata(d);
-	struct nouveau_drm *drm = nouveau_drm(dev);
-	struct nvkm_therm *therm = nvxx_therm(&drm->client.device);
+static const struct hwmon_channel_info nouveau_chip = {
+	.type = hwmon_chip,
+	.config = nouveau_config_chip,
+};
 
-	return snprintf(buf, PAGE_SIZE, "%d\n",
-	  therm->attr_get(therm, NVKM_THERM_ATTR_THRS_SHUTDOWN_HYST) * 1000);
-}
-static ssize_t
-nouveau_hwmon_set_emergency_temp_hyst(struct device *d,
-				      struct device_attribute *a,
-				      const char *buf,
-				      size_t count)
-{
-	struct drm_device *dev = dev_get_drvdata(d);
-	struct nouveau_drm *drm = nouveau_drm(dev);
-	struct nvkm_therm *therm = nvxx_therm(&drm->client.device);
-	long value;
+static const struct hwmon_channel_info nouveau_temp = {
+	.type = hwmon_temp,
+	.config = nouveau_config_temp,
+};
 
-	if (kstrtol(buf, 10, &value) == -EINVAL)
-		return count;
+static const struct hwmon_channel_info nouveau_fan = {
+	.type = hwmon_fan,
+	.config = nouveau_config_fan,
+};
 
-	therm->attr_set(therm, NVKM_THERM_ATTR_THRS_SHUTDOWN_HYST,
-			value / 1000);
+static const struct hwmon_channel_info nouveau_in = {
+	.type = hwmon_in,
+	.config = nouveau_config_in,
+};
 
-	return count;
-}
-static SENSOR_DEVICE_ATTR(temp1_emergency_hyst, S_IRUGO | S_IWUSR,
-					nouveau_hwmon_emergency_temp_hyst,
-					nouveau_hwmon_set_emergency_temp_hyst,
-					0);
-
-static ssize_t nouveau_hwmon_show_name(struct device *dev,
-				      struct device_attribute *attr,
-				      char *buf)
+static const struct hwmon_channel_info nouveau_pwm = {
+	.type = hwmon_pwm,
+	.config = nouveau_config_pwm,
+};
+
+static const struct hwmon_channel_info nouveau_power = {
+	.type = hwmon_power,
+	.config = nouveau_config_power,
+};
+
+static const struct hwmon_channel_info *nouveau_info[] = {
+	&nouveau_chip,
+	&nouveau_temp,
+	&nouveau_fan,
+	&nouveau_in,
+	&nouveau_pwm,
+	&nouveau_power,
+	NULL
+};
+
+static umode_t
+nouveau_chip_is_visible(const void *data, u32 attr, int channel)
 {
-	return sprintf(buf, "nouveau\n");
+	switch (attr) {
+	case hwmon_chip_update_interval:
+		return 0444;
+	default:
+		return 0;
+	}
 }
-static SENSOR_DEVICE_ATTR(name, S_IRUGO, nouveau_hwmon_show_name, NULL, 0);
 
-static ssize_t nouveau_hwmon_show_update_rate(struct device *dev,
-				      struct device_attribute *attr,
-				      char *buf)
+static umode_t
+nouveau_power_is_visible(const void *data, u32 attr, int channel)
 {
-	return sprintf(buf, "1000\n");
+	struct nouveau_drm *drm = nouveau_drm((struct drm_device *)data);
+	struct nvkm_iccsense *iccsense = nvxx_iccsense(&drm->client.device);
+
+	if (!iccsense || !iccsense->data_valid || list_empty(&iccsense->rails))
+		return 0;
+
+	switch (attr) {
+	case hwmon_power_input:
+		return 0444;
+	case hwmon_power_max:
+		if (iccsense->power_w_max)
+			return 0444;
+		return 0;
+	case hwmon_power_crit:
+		if (iccsense->power_w_crit)
+			return 0444;
+		return 0;
+	default:
+		return 0;
+	}
 }
-static SENSOR_DEVICE_ATTR(update_rate, S_IRUGO,
-						nouveau_hwmon_show_update_rate,
-						NULL, 0);
 
-static ssize_t
-nouveau_hwmon_show_fan1_input(struct device *d, struct device_attribute *attr,
-			      char *buf)
+static umode_t
+nouveau_temp_is_visible(const void *data, u32 attr, int channel)
 {
-	struct drm_device *dev = dev_get_drvdata(d);
-	struct nouveau_drm *drm = nouveau_drm(dev);
+	struct nouveau_drm *drm = nouveau_drm((struct drm_device *)data);
 	struct nvkm_therm *therm = nvxx_therm(&drm->client.device);
 
-	return snprintf(buf, PAGE_SIZE, "%d\n", nvkm_therm_fan_sense(therm));
+	if (therm && therm->attr_get && nvkm_therm_temp_get(therm) < 0)
+		return 0;
+
+	switch (attr) {
+	case hwmon_temp_input:
+		return 0444;
+	case hwmon_temp_max:
+	case hwmon_temp_max_hyst:
+	case hwmon_temp_crit:
+	case hwmon_temp_crit_hyst:
+	case hwmon_temp_emergency:
+	case hwmon_temp_emergency_hyst:
+		return 0644;
+	default:
+		return 0;
+	}
 }
-static SENSOR_DEVICE_ATTR(fan1_input, S_IRUGO, nouveau_hwmon_show_fan1_input,
-			  NULL, 0);
 
- static ssize_t
-nouveau_hwmon_get_pwm1_enable(struct device *d,
-			   struct device_attribute *a, char *buf)
+static umode_t
+nouveau_pwm_is_visible(const void *data, u32 attr, int channel)
 {
-	struct drm_device *dev = dev_get_drvdata(d);
-	struct nouveau_drm *drm = nouveau_drm(dev);
+	struct nouveau_drm *drm = nouveau_drm((struct drm_device *)data);
 	struct nvkm_therm *therm = nvxx_therm(&drm->client.device);
-	int ret;
 
-	ret = therm->attr_get(therm, NVKM_THERM_ATTR_FAN_MODE);
-	if (ret < 0)
-		return ret;
+	if (therm && therm->attr_get && therm->fan_get &&
+				therm->fan_get(therm) < 0)
+		return 0;
 
-	return sprintf(buf, "%i\n", ret);
+	switch (attr) {
+	case hwmon_pwm_enable:
+	case hwmon_pwm_input:
+		return 0644;
+	default:
+		return 0;
+	}
 }
 
-static ssize_t
-nouveau_hwmon_set_pwm1_enable(struct device *d, struct device_attribute *a,
-			   const char *buf, size_t count)
+static umode_t
+nouveau_input_is_visible(const void *data, u32 attr, int channel)
 {
-	struct drm_device *dev = dev_get_drvdata(d);
-	struct nouveau_drm *drm = nouveau_drm(dev);
-	struct nvkm_therm *therm = nvxx_therm(&drm->client.device);
-	long value;
-	int ret;
-
-	ret = kstrtol(buf, 10, &value);
-	if (ret)
-		return ret;
+	struct nouveau_drm *drm = nouveau_drm((struct drm_device *)data);
+	struct nvkm_volt *volt = nvxx_volt(&drm->client.device);
 
-	ret = therm->attr_set(therm, NVKM_THERM_ATTR_FAN_MODE, value);
-	if (ret)
-		return ret;
-	else
-		return count;
+	if (!volt || nvkm_volt_get(volt) < 0)
+		return 0;
+
+	switch (attr) {
+	case hwmon_in_input:
+	case hwmon_in_label:
+	case hwmon_in_min:
+	case hwmon_in_max:
+		return 0444;
+	default:
+		return 0;
+	}
 }
-static SENSOR_DEVICE_ATTR(pwm1_enable, S_IRUGO | S_IWUSR,
-			  nouveau_hwmon_get_pwm1_enable,
-			  nouveau_hwmon_set_pwm1_enable, 0);
 
-static ssize_t
-nouveau_hwmon_get_pwm1(struct device *d, struct device_attribute *a, char *buf)
+static umode_t
+nouveau_fan_is_visible(const void *data, u32 attr, int channel)
 {
-	struct drm_device *dev = dev_get_drvdata(d);
-	struct nouveau_drm *drm = nouveau_drm(dev);
+	struct nouveau_drm *drm = nouveau_drm((struct drm_device *)data);
 	struct nvkm_therm *therm = nvxx_therm(&drm->client.device);
-	int ret;
 
-	ret = therm->fan_get(therm);
-	if (ret < 0)
-		return ret;
+	if (!therm || !therm->attr_get || nvkm_therm_fan_sense(therm) < 0)
+		return 0;
 
-	return sprintf(buf, "%i\n", ret);
+	switch (attr) {
+	case hwmon_fan_input:
+		return 0444;
+	default:
+		return 0;
+	}
 }
 
-static ssize_t
-nouveau_hwmon_set_pwm1(struct device *d, struct device_attribute *a,
-		       const char *buf, size_t count)
+static int
+nouveau_chip_read(struct device *dev, u32 attr, int channel, long *val)
 {
-	struct drm_device *dev = dev_get_drvdata(d);
-	struct nouveau_drm *drm = nouveau_drm(dev);
-	struct nvkm_therm *therm = nvxx_therm(&drm->client.device);
-	int ret = -ENODEV;
-	long value;
-
-	if (kstrtol(buf, 10, &value) == -EINVAL)
-		return -EINVAL;
-
-	ret = therm->fan_set(therm, value);
-	if (ret)
-		return ret;
+	switch (attr) {
+	case hwmon_chip_update_interval:
+		*val = 1000;
+		break;
+	default:
+		return -EOPNOTSUPP;
+	}
 
-	return count;
+	return 0;
 }
 
-static SENSOR_DEVICE_ATTR(pwm1, S_IRUGO | S_IWUSR,
-			  nouveau_hwmon_get_pwm1,
-			  nouveau_hwmon_set_pwm1, 0);
-
-static ssize_t
-nouveau_hwmon_get_pwm1_min(struct device *d,
-			   struct device_attribute *a, char *buf)
+static int
+nouveau_temp_read(struct device *dev, u32 attr, int channel, long *val)
 {
-	struct drm_device *dev = dev_get_drvdata(d);
-	struct nouveau_drm *drm = nouveau_drm(dev);
+	struct drm_device *drm_dev = dev_get_drvdata(dev);
+	struct nouveau_drm *drm = nouveau_drm(drm_dev);
 	struct nvkm_therm *therm = nvxx_therm(&drm->client.device);
 	int ret;
 
-	ret = therm->attr_get(therm, NVKM_THERM_ATTR_FAN_MIN_DUTY);
-	if (ret < 0)
-		return ret;
+	if (!therm || !therm->attr_get)
+		return -EOPNOTSUPP;
+
+	switch (attr) {
+	case hwmon_temp_input:
+		ret = nvkm_therm_temp_get(therm);
+		*val = ret < 0 ? ret : (ret * 1000);
+		break;
+	case hwmon_temp_max:
+		*val = therm->attr_get(therm, NVKM_THERM_ATTR_THRS_DOWN_CLK)
+					* 1000;
+		break;
+	case hwmon_temp_max_hyst:
+		*val = therm->attr_get(therm, NVKM_THERM_ATTR_THRS_DOWN_CLK_HYST)
+					* 1000;
+		break;
+	case hwmon_temp_crit:
+		*val = therm->attr_get(therm, NVKM_THERM_ATTR_THRS_CRITICAL)
+					* 1000;
+		break;
+	case hwmon_temp_crit_hyst:
+		*val = therm->attr_get(therm, NVKM_THERM_ATTR_THRS_CRITICAL_HYST)
+					* 1000;
+		break;
+	case hwmon_temp_emergency:
+		*val = therm->attr_get(therm, NVKM_THERM_ATTR_THRS_SHUTDOWN)
+					* 1000;
+		break;
+	case hwmon_temp_emergency_hyst:
+		*val = therm->attr_get(therm, NVKM_THERM_ATTR_THRS_SHUTDOWN_HYST)
+					* 1000;
+		break;
+	default:
+		return -EOPNOTSUPP;
+	}
 
-	return sprintf(buf, "%i\n", ret);
+	return 0;
 }
 
-static ssize_t
-nouveau_hwmon_set_pwm1_min(struct device *d, struct device_attribute *a,
-			   const char *buf, size_t count)
+static int
+nouveau_fan_read(struct device *dev, u32 attr, int channel, long *val)
 {
-	struct drm_device *dev = dev_get_drvdata(d);
-	struct nouveau_drm *drm = nouveau_drm(dev);
+	struct drm_device *drm_dev = dev_get_drvdata(dev);
+	struct nouveau_drm *drm = nouveau_drm(drm_dev);
 	struct nvkm_therm *therm = nvxx_therm(&drm->client.device);
-	long value;
-	int ret;
 
-	if (kstrtol(buf, 10, &value) == -EINVAL)
-		return -EINVAL;
+	if (!therm)
+		return -EOPNOTSUPP;
 
-	ret = therm->attr_set(therm, NVKM_THERM_ATTR_FAN_MIN_DUTY, value);
-	if (ret < 0)
-		return ret;
+	switch (attr) {
+	case hwmon_fan_input:
+		*val = nvkm_therm_fan_sense(therm);
+		break;
+	default:
+		return -EOPNOTSUPP;
+	}
 
-	return count;
+	return 0;
 }
 
-static SENSOR_DEVICE_ATTR(pwm1_min, S_IRUGO | S_IWUSR,
-			  nouveau_hwmon_get_pwm1_min,
-			  nouveau_hwmon_set_pwm1_min, 0);
-
-static ssize_t
-nouveau_hwmon_get_pwm1_max(struct device *d,
-			   struct device_attribute *a, char *buf)
+static int
+nouveau_in_read(struct device *dev, u32 attr, int channel, long *val)
 {
-	struct drm_device *dev = dev_get_drvdata(d);
-	struct nouveau_drm *drm = nouveau_drm(dev);
-	struct nvkm_therm *therm = nvxx_therm(&drm->client.device);
+	struct drm_device *drm_dev = dev_get_drvdata(dev);
+	struct nouveau_drm *drm = nouveau_drm(drm_dev);
+	struct nvkm_volt *volt = nvxx_volt(&drm->client.device);
 	int ret;
 
-	ret = therm->attr_get(therm, NVKM_THERM_ATTR_FAN_MAX_DUTY);
-	if (ret < 0)
-		return ret;
+	if (!volt)
+		return -EOPNOTSUPP;
+
+	switch (attr) {
+	case hwmon_in_input:
+		ret = nvkm_volt_get(volt);
+		*val = ret < 0 ? ret : (ret / 1000);
+		break;
+	case hwmon_in_min:
+		*val = volt->min_uv > 0 ? (volt->min_uv / 1000) : -ENODEV;
+		break;
+	case hwmon_in_max:
+		*val = volt->max_uv > 0 ? (volt->max_uv / 1000) : -ENODEV;
+		break;
+	default:
+		return -EOPNOTSUPP;
+	}
 
-	return sprintf(buf, "%i\n", ret);
+	return 0;
 }
 
-static ssize_t
-nouveau_hwmon_set_pwm1_max(struct device *d, struct device_attribute *a,
-			   const char *buf, size_t count)
+static int
+nouveau_pwm_read(struct device *dev, u32 attr, int channel, long *val)
 {
-	struct drm_device *dev = dev_get_drvdata(d);
-	struct nouveau_drm *drm = nouveau_drm(dev);
+	struct drm_device *drm_dev = dev_get_drvdata(dev);
+	struct nouveau_drm *drm = nouveau_drm(drm_dev);
 	struct nvkm_therm *therm = nvxx_therm(&drm->client.device);
-	long value;
-	int ret;
 
-	if (kstrtol(buf, 10, &value) == -EINVAL)
-		return -EINVAL;
-
-	ret = therm->attr_set(therm, NVKM_THERM_ATTR_FAN_MAX_DUTY, value);
-	if (ret < 0)
-		return ret;
+	if (!therm || !therm->attr_get || !therm->fan_get)
+		return -EOPNOTSUPP;
+
+	switch (attr) {
+	case hwmon_pwm_enable:
+		*val = therm->attr_get(therm, NVKM_THERM_ATTR_FAN_MODE);
+		break;
+	case hwmon_pwm_input:
+		*val = therm->fan_get(therm);
+		break;
+	default:
+		return -EOPNOTSUPP;
+	}
 
-	return count;
+	return 0;
 }
 
-static SENSOR_DEVICE_ATTR(pwm1_max, S_IRUGO | S_IWUSR,
-			  nouveau_hwmon_get_pwm1_max,
-			  nouveau_hwmon_set_pwm1_max, 0);
-
-static ssize_t
-nouveau_hwmon_get_in0_input(struct device *d,
-			    struct device_attribute *a, char *buf)
+static int
+nouveau_power_read(struct device *dev, u32 attr, int channel, long *val)
 {
-	struct drm_device *dev = dev_get_drvdata(d);
-	struct nouveau_drm *drm = nouveau_drm(dev);
-	struct nvkm_volt *volt = nvxx_volt(&drm->client.device);
-	int ret;
+	struct drm_device *drm_dev = dev_get_drvdata(dev);
+	struct nouveau_drm *drm = nouveau_drm(drm_dev);
+	struct nvkm_iccsense *iccsense = nvxx_iccsense(&drm->client.device);
 
-	ret = nvkm_volt_get(volt);
-	if (ret < 0)
-		return ret;
+	if (!iccsense)
+		return -EOPNOTSUPP;
+
+	switch (attr) {
+	case hwmon_power_input:
+		*val = nvkm_iccsense_read_all(iccsense);
+		break;
+	case hwmon_power_max:
+		*val = iccsense->power_w_max;
+		break;
+	case hwmon_power_crit:
+		*val = iccsense->power_w_crit;
+		break;
+	default:
+		return -EOPNOTSUPP;
+	}
 
-	return sprintf(buf, "%i\n", ret / 1000);
+	return 0;
 }
 
-static SENSOR_DEVICE_ATTR(in0_input, S_IRUGO,
-			  nouveau_hwmon_get_in0_input, NULL, 0);
-
-static ssize_t
-nouveau_hwmon_get_in0_min(struct device *d,
-			    struct device_attribute *a, char *buf)
+static int
+nouveau_temp_write(struct device *dev, u32 attr, int channel, long val)
 {
-	struct drm_device *dev = dev_get_drvdata(d);
-	struct nouveau_drm *drm = nouveau_drm(dev);
-	struct nvkm_volt *volt = nvxx_volt(&drm->client.device);
-
-	if (!volt || !volt->min_uv)
-		return -ENODEV;
+	struct drm_device *drm_dev = dev_get_drvdata(dev);
+	struct nouveau_drm *drm = nouveau_drm(drm_dev);
+	struct nvkm_therm *therm = nvxx_therm(&drm->client.device);
 
-	return sprintf(buf, "%i\n", volt->min_uv / 1000);
+	if (!therm || !therm->attr_set)
+		return -EOPNOTSUPP;
+
+	switch (attr) {
+	case hwmon_temp_max:
+		return therm->attr_set(therm, NVKM_THERM_ATTR_THRS_DOWN_CLK,
+					val / 1000);
+	case hwmon_temp_max_hyst:
+		return therm->attr_set(therm, NVKM_THERM_ATTR_THRS_DOWN_CLK_HYST,
+					val / 1000);
+	case hwmon_temp_crit:
+		return therm->attr_set(therm, NVKM_THERM_ATTR_THRS_CRITICAL,
+					val / 1000);
+	case hwmon_temp_crit_hyst:
+		return therm->attr_set(therm, NVKM_THERM_ATTR_THRS_CRITICAL_HYST,
+					val / 1000);
+	case hwmon_temp_emergency:
+		return therm->attr_set(therm, NVKM_THERM_ATTR_THRS_SHUTDOWN,
+					val / 1000);
+	case hwmon_temp_emergency_hyst:
+		return therm->attr_set(therm, NVKM_THERM_ATTR_THRS_SHUTDOWN_HYST,
+					val / 1000);
+	default:
+		return -EOPNOTSUPP;
+	}
 }
 
-static SENSOR_DEVICE_ATTR(in0_min, S_IRUGO,
-			  nouveau_hwmon_get_in0_min, NULL, 0);
-
-static ssize_t
-nouveau_hwmon_get_in0_max(struct device *d,
-			    struct device_attribute *a, char *buf)
+static int
+nouveau_pwm_write(struct device *dev, u32 attr, int channel, long val)
 {
-	struct drm_device *dev = dev_get_drvdata(d);
-	struct nouveau_drm *drm = nouveau_drm(dev);
-	struct nvkm_volt *volt = nvxx_volt(&drm->client.device);
+	struct drm_device *drm_dev = dev_get_drvdata(dev);
+	struct nouveau_drm *drm = nouveau_drm(drm_dev);
+	struct nvkm_therm *therm = nvxx_therm(&drm->client.device);
 
-	if (!volt || !volt->max_uv)
-		return -ENODEV;
+	if (!therm || !therm->attr_set)
+		return -EOPNOTSUPP;
 
-	return sprintf(buf, "%i\n", volt->max_uv / 1000);
+	switch (attr) {
+	case hwmon_pwm_input:
+		return therm->fan_set(therm, val);
+	case hwmon_pwm_enable:
+		return therm->attr_set(therm, NVKM_THERM_ATTR_FAN_MODE, val);
+	default:
+		return -EOPNOTSUPP;
+	}
 }
 
-static SENSOR_DEVICE_ATTR(in0_max, S_IRUGO,
-			  nouveau_hwmon_get_in0_max, NULL, 0);
-
-static ssize_t
-nouveau_hwmon_get_in0_label(struct device *d,
-			    struct device_attribute *a, char *buf)
-{
-	return sprintf(buf, "GPU core\n");
+static umode_t
+nouveau_is_visible(const void *data, enum hwmon_sensor_types type, u32 attr,
+			int channel)
+{
+	switch (type) {
+	case hwmon_chip:
+		return nouveau_chip_is_visible(data, attr, channel);
+	case hwmon_temp:
+		return nouveau_temp_is_visible(data, attr, channel);
+	case hwmon_fan:
+		return nouveau_fan_is_visible(data, attr, channel);
+	case hwmon_in:
+		return nouveau_input_is_visible(data, attr, channel);
+	case hwmon_pwm:
+		return nouveau_pwm_is_visible(data, attr, channel);
+	case hwmon_power:
+		return nouveau_power_is_visible(data, attr, channel);
+	default:
+		return 0;
+	}
 }
 
-static SENSOR_DEVICE_ATTR(in0_label, S_IRUGO,
-			  nouveau_hwmon_get_in0_label, NULL, 0);
+static const char input_label[] = "GPU core";
 
-static ssize_t
-nouveau_hwmon_get_power1_input(struct device *d, struct device_attribute *a,
-			       char *buf)
+static int
+nouveau_read_string(struct device *dev, enum hwmon_sensor_types type, u32 attr,
+		    int channel, const char **buf)
 {
-	struct drm_device *dev = dev_get_drvdata(d);
-	struct nouveau_drm *drm = nouveau_drm(dev);
-	struct nvkm_iccsense *iccsense = nvxx_iccsense(&drm->client.device);
-	int result = nvkm_iccsense_read_all(iccsense);
-
-	if (result < 0)
-		return result;
-
-	return sprintf(buf, "%i\n", result);
-}
-
-static SENSOR_DEVICE_ATTR(power1_input, S_IRUGO,
-			  nouveau_hwmon_get_power1_input, NULL, 0);
+	if (type == hwmon_in && attr == hwmon_in_label) {
+		*buf = input_label;
+		return 0;
+	}
 
-static ssize_t
-nouveau_hwmon_get_power1_max(struct device *d, struct device_attribute *a,
-			     char *buf)
-{
-	struct drm_device *dev = dev_get_drvdata(d);
-	struct nouveau_drm *drm = nouveau_drm(dev);
-	struct nvkm_iccsense *iccsense = nvxx_iccsense(&drm->client.device);
-	return sprintf(buf, "%i\n", iccsense->power_w_max);
+	return -EOPNOTSUPP;
+}
+
+static int
+nouveau_read(struct device *dev, enum hwmon_sensor_types type, u32 attr,
+							int channel, long *val)
+{
+	switch (type) {
+	case hwmon_chip:
+		return nouveau_chip_read(dev, attr, channel, val);
+	case hwmon_temp:
+		return nouveau_temp_read(dev, attr, channel, val);
+	case hwmon_fan:
+		return nouveau_fan_read(dev, attr, channel, val);
+	case hwmon_in:
+		return nouveau_in_read(dev, attr, channel, val);
+	case hwmon_pwm:
+		return nouveau_pwm_read(dev, attr, channel, val);
+	case hwmon_power:
+		return nouveau_power_read(dev, attr, channel, val);
+	default:
+		return -EOPNOTSUPP;
+	}
 }
 
-static SENSOR_DEVICE_ATTR(power1_max, S_IRUGO,
-			  nouveau_hwmon_get_power1_max, NULL, 0);
-
-static ssize_t
-nouveau_hwmon_get_power1_crit(struct device *d, struct device_attribute *a,
-			      char *buf)
+static int
+nouveau_write(struct device *dev, enum hwmon_sensor_types type, u32 attr,
+							int channel, long val)
 {
-	struct drm_device *dev = dev_get_drvdata(d);
-	struct nouveau_drm *drm = nouveau_drm(dev);
-	struct nvkm_iccsense *iccsense = nvxx_iccsense(&drm->client.device);
-	return sprintf(buf, "%i\n", iccsense->power_w_crit);
+	switch (type) {
+	case hwmon_temp:
+		return nouveau_temp_write(dev, attr, channel, val);
+	case hwmon_pwm:
+		return nouveau_pwm_write(dev, attr, channel, val);
+	default:
+		return -EOPNOTSUPP;
+	}
 }
 
-static SENSOR_DEVICE_ATTR(power1_crit, S_IRUGO,
-			  nouveau_hwmon_get_power1_crit, NULL, 0);
-
-static struct attribute *hwmon_default_attributes[] = {
-	&sensor_dev_attr_name.dev_attr.attr,
-	&sensor_dev_attr_update_rate.dev_attr.attr,
-	NULL
-};
-static struct attribute *hwmon_temp_attributes[] = {
-	&sensor_dev_attr_temp1_input.dev_attr.attr,
-	&sensor_dev_attr_temp1_auto_point1_pwm.dev_attr.attr,
-	&sensor_dev_attr_temp1_auto_point1_temp.dev_attr.attr,
-	&sensor_dev_attr_temp1_auto_point1_temp_hyst.dev_attr.attr,
-	&sensor_dev_attr_temp1_max.dev_attr.attr,
-	&sensor_dev_attr_temp1_max_hyst.dev_attr.attr,
-	&sensor_dev_attr_temp1_crit.dev_attr.attr,
-	&sensor_dev_attr_temp1_crit_hyst.dev_attr.attr,
-	&sensor_dev_attr_temp1_emergency.dev_attr.attr,
-	&sensor_dev_attr_temp1_emergency_hyst.dev_attr.attr,
-	NULL
-};
-static struct attribute *hwmon_fan_rpm_attributes[] = {
-	&sensor_dev_attr_fan1_input.dev_attr.attr,
-	NULL
-};
-static struct attribute *hwmon_pwm_fan_attributes[] = {
-	&sensor_dev_attr_pwm1_enable.dev_attr.attr,
-	&sensor_dev_attr_pwm1.dev_attr.attr,
-	&sensor_dev_attr_pwm1_min.dev_attr.attr,
-	&sensor_dev_attr_pwm1_max.dev_attr.attr,
-	NULL
-};
-
-static struct attribute *hwmon_in0_attributes[] = {
-	&sensor_dev_attr_in0_input.dev_attr.attr,
-	&sensor_dev_attr_in0_min.dev_attr.attr,
-	&sensor_dev_attr_in0_max.dev_attr.attr,
-	&sensor_dev_attr_in0_label.dev_attr.attr,
-	NULL
+static const struct hwmon_ops nouveau_hwmon_ops = {
+	.is_visible = nouveau_is_visible,
+	.read = nouveau_read,
+	.read_string = nouveau_read_string,
+	.write = nouveau_write,
 };
 
-static struct attribute *hwmon_power_attributes[] = {
-	&sensor_dev_attr_power1_input.dev_attr.attr,
-	NULL
-};
-
-static struct attribute *hwmon_power_caps_attributes[] = {
-	&sensor_dev_attr_power1_max.dev_attr.attr,
-	&sensor_dev_attr_power1_crit.dev_attr.attr,
-	NULL
-};
-
-static const struct attribute_group hwmon_default_attrgroup = {
-	.attrs = hwmon_default_attributes,
-};
-static const struct attribute_group hwmon_temp_attrgroup = {
-	.attrs = hwmon_temp_attributes,
-};
-static const struct attribute_group hwmon_fan_rpm_attrgroup = {
-	.attrs = hwmon_fan_rpm_attributes,
-};
-static const struct attribute_group hwmon_pwm_fan_attrgroup = {
-	.attrs = hwmon_pwm_fan_attributes,
-};
-static const struct attribute_group hwmon_in0_attrgroup = {
-	.attrs = hwmon_in0_attributes,
-};
-static const struct attribute_group hwmon_power_attrgroup = {
-	.attrs = hwmon_power_attributes,
-};
-static const struct attribute_group hwmon_power_caps_attrgroup = {
-	.attrs = hwmon_power_caps_attributes,
+static const struct hwmon_chip_info nouveau_chip_info = {
+	.ops = &nouveau_hwmon_ops,
+	.info = nouveau_info,
 };
 #endif
 
@@ -700,90 +708,36 @@ nouveau_hwmon_init(struct drm_device *dev)
 #if defined(CONFIG_HWMON) || (defined(MODULE) && defined(CONFIG_HWMON_MODULE))
 	struct nouveau_drm *drm = nouveau_drm(dev);
 	struct nvkm_therm *therm = nvxx_therm(&drm->client.device);
-	struct nvkm_volt *volt = nvxx_volt(&drm->client.device);
-	struct nvkm_iccsense *iccsense = nvxx_iccsense(&drm->client.device);
+	const struct attribute_group *special_groups[N_ATTR_GROUPS];
 	struct nouveau_hwmon *hwmon;
 	struct device *hwmon_dev;
 	int ret = 0;
+	int i = 0;
 
 	hwmon = drm->hwmon = kzalloc(sizeof(*hwmon), GFP_KERNEL);
 	if (!hwmon)
 		return -ENOMEM;
 	hwmon->dev = dev;
 
-	hwmon_dev = hwmon_device_register(dev->dev);
+	if (therm && therm->attr_get && therm->attr_set) {
+		if (nvkm_therm_temp_get(therm) >= 0)
+			special_groups[i++] = &temp1_auto_point_sensor_group;
+		if (therm->fan_get && therm->fan_get(therm) >= 0)
+			special_groups[i++] = &pwm_fan_sensor_group;
+	}
+
+	special_groups[i] = 0;
+	hwmon_dev = hwmon_device_register_with_info(dev->dev, "nouveau", dev,
+							&nouveau_chip_info,
+							special_groups);
 	if (IS_ERR(hwmon_dev)) {
 		ret = PTR_ERR(hwmon_dev);
 		NV_ERROR(drm, "Unable to register hwmon device: %d\n", ret);
 		return ret;
 	}
-	dev_set_drvdata(hwmon_dev, dev);
-
-	/* set the default attributes */
-	ret = sysfs_create_group(&hwmon_dev->kobj, &hwmon_default_attrgroup);
-	if (ret)
-		goto error;
-
-	if (therm && therm->attr_get && therm->attr_set) {
-		/* if the card has a working thermal sensor */
-		if (nvkm_therm_temp_get(therm) >= 0) {
-			ret = sysfs_create_group(&hwmon_dev->kobj, &hwmon_temp_attrgroup);
-			if (ret)
-				goto error;
-		}
-
-		/* if the card has a pwm fan */
-		/*XXX: incorrect, need better detection for this, some boards have
-		 *     the gpio entries for pwm fan control even when there's no
-		 *     actual fan connected to it... therm table? */
-		if (therm->fan_get && therm->fan_get(therm) >= 0) {
-			ret = sysfs_create_group(&hwmon_dev->kobj,
-						 &hwmon_pwm_fan_attrgroup);
-			if (ret)
-				goto error;
-		}
-	}
-
-	/* if the card can read the fan rpm */
-	if (therm && nvkm_therm_fan_sense(therm) >= 0) {
-		ret = sysfs_create_group(&hwmon_dev->kobj,
-					 &hwmon_fan_rpm_attrgroup);
-		if (ret)
-			goto error;
-	}
-
-	if (volt && nvkm_volt_get(volt) >= 0) {
-		ret = sysfs_create_group(&hwmon_dev->kobj,
-					 &hwmon_in0_attrgroup);
-
-		if (ret)
-			goto error;
-	}
-
-	if (iccsense && iccsense->data_valid && !list_empty(&iccsense->rails)) {
-		ret = sysfs_create_group(&hwmon_dev->kobj,
-					 &hwmon_power_attrgroup);
-
-		if (ret)
-			goto error;
-
-		if (iccsense->power_w_max && iccsense->power_w_crit) {
-			ret = sysfs_create_group(&hwmon_dev->kobj,
-						 &hwmon_power_caps_attrgroup);
-			if (ret)
-				goto error;
-		}
-	}
 
 	hwmon->hwmon = hwmon_dev;
-
 	return 0;
-
-error:
-	NV_ERROR(drm, "Unable to create some hwmon sysfs files: %d\n", ret);
-	hwmon_device_unregister(hwmon_dev);
-	hwmon->hwmon = NULL;
-	return ret;
 #else
 	return 0;
 #endif
@@ -795,17 +749,8 @@ nouveau_hwmon_fini(struct drm_device *dev)
 #if defined(CONFIG_HWMON) || (defined(MODULE) && defined(CONFIG_HWMON_MODULE))
 	struct nouveau_hwmon *hwmon = nouveau_hwmon(dev);
 
-	if (hwmon->hwmon) {
-		sysfs_remove_group(&hwmon->hwmon->kobj, &hwmon_default_attrgroup);
-		sysfs_remove_group(&hwmon->hwmon->kobj, &hwmon_temp_attrgroup);
-		sysfs_remove_group(&hwmon->hwmon->kobj, &hwmon_pwm_fan_attrgroup);
-		sysfs_remove_group(&hwmon->hwmon->kobj, &hwmon_fan_rpm_attrgroup);
-		sysfs_remove_group(&hwmon->hwmon->kobj, &hwmon_in0_attrgroup);
-		sysfs_remove_group(&hwmon->hwmon->kobj, &hwmon_power_attrgroup);
-		sysfs_remove_group(&hwmon->hwmon->kobj, &hwmon_power_caps_attrgroup);
-
+	if (hwmon->hwmon)
 		hwmon_device_unregister(hwmon->hwmon);
-	}
 
 	nouveau_drm(dev)->hwmon = NULL;
 	kfree(hwmon);
diff --git a/drivers/gpu/drm/nouveau/nouveau_ttm.c b/drivers/gpu/drm/nouveau/nouveau_ttm.c
index 13e5cc5f07fe..999c35a25498 100644
--- a/drivers/gpu/drm/nouveau/nouveau_ttm.c
+++ b/drivers/gpu/drm/nouveau/nouveau_ttm.c
@@ -28,7 +28,7 @@
 #include "nouveau_ttm.h"
 #include "nouveau_gem.h"
 
-#include "drm_legacy.h"
+#include <drm/drm_legacy.h>
 
 #include <core/tegra.h>
 
diff --git a/drivers/gpu/drm/nouveau/nouveau_vga.c b/drivers/gpu/drm/nouveau/nouveau_vga.c
index 02fe0efb9e16..48393a4f6331 100644
--- a/drivers/gpu/drm/nouveau/nouveau_vga.c
+++ b/drivers/gpu/drm/nouveau/nouveau_vga.c
@@ -111,6 +111,10 @@ nouveau_vga_fini(struct nouveau_drm *drm)
 	struct drm_device *dev = drm->dev;
 	bool runtime = nouveau_pmops_runtime();
 
+	/* only relevant for PCI devices */
+	if (!dev->pdev)
+		return;
+
 	vga_client_register(dev->pdev, NULL, NULL, NULL);
 
 	if (pci_is_thunderbolt_attached(dev->pdev))
diff --git a/drivers/gpu/drm/nouveau/nv50_display.c b/drivers/gpu/drm/nouveau/nv50_display.c
index 06e564a9ccb2..e3132a2ce34d 100644
--- a/drivers/gpu/drm/nouveau/nv50_display.c
+++ b/drivers/gpu/drm/nouveau/nv50_display.c
@@ -23,6 +23,7 @@
  */
 
 #include <linux/dma-mapping.h>
+#include <linux/hdmi.h>
 
 #include <drm/drmP.h>
 #include <drm/drm_atomic.h>
@@ -31,6 +32,7 @@
 #include <drm/drm_dp_helper.h>
 #include <drm/drm_fb_helper.h>
 #include <drm/drm_plane_helper.h>
+#include <drm/drm_edid.h>
 
 #include <nvif/class.h>
 #include <nvif/cl0002.h>
@@ -1033,7 +1035,7 @@ nv50_wndw_reset(struct drm_plane *plane)
 		plane->funcs->atomic_destroy_state(plane, plane->state);
 	plane->state = &asyw->state;
 	plane->state->plane = plane;
-	plane->state->rotation = DRM_ROTATE_0;
+	plane->state->rotation = DRM_MODE_ROTATE_0;
 }
 
 static void
@@ -1965,6 +1967,7 @@ nv50_head_atomic_check_view(struct nv50_head_atom *armh,
 	struct drm_display_mode *umode = &asyh->state.mode;
 	int mode = asyc->scaler.mode;
 	struct edid *edid;
+	int umode_vdisplay, omode_hdisplay, omode_vdisplay;
 
 	if (connector->edid_blob_ptr)
 		edid = (struct edid *)connector->edid_blob_ptr->data;
@@ -1979,12 +1982,18 @@ nv50_head_atomic_check_view(struct nv50_head_atom *armh,
 		mode = DRM_MODE_SCALE_FULLSCREEN;
 	}
 
+	/* For the user-specified mode, we must ignore doublescan and
+	 * the like, but honor frame packing.
+	 */
+	umode_vdisplay = umode->vdisplay;
+	if ((umode->flags & DRM_MODE_FLAG_3D_MASK) == DRM_MODE_FLAG_3D_FRAME_PACKING)
+		umode_vdisplay += umode->vtotal;
 	asyh->view.iW = umode->hdisplay;
-	asyh->view.iH = umode->vdisplay;
-	asyh->view.oW = omode->hdisplay;
-	asyh->view.oH = omode->vdisplay;
-	if (omode->flags & DRM_MODE_FLAG_DBLSCAN)
-		asyh->view.oH *= 2;
+	asyh->view.iH = umode_vdisplay;
+	/* For the output mode, we can just use the stock helper. */
+	drm_mode_get_hv_timing(omode, &omode_hdisplay, &omode_vdisplay);
+	asyh->view.oW = omode_hdisplay;
+	asyh->view.oH = omode_vdisplay;
 
 	/* Add overscan compensation if necessary, will keep the aspect
 	 * ratio the same as the backend mode unless overridden by the
@@ -2014,7 +2023,7 @@ nv50_head_atomic_check_view(struct nv50_head_atom *armh,
 	switch (mode) {
 	case DRM_MODE_SCALE_CENTER:
 		asyh->view.oW = min((u16)umode->hdisplay, asyh->view.oW);
-		asyh->view.oH = min((u16)umode->vdisplay, asyh->view.oH);
+		asyh->view.oH = min((u16)umode_vdisplay, asyh->view.oH);
 		/* fall-through */
 	case DRM_MODE_SCALE_ASPECT:
 		if (asyh->view.oH < asyh->view.oW) {
@@ -2036,34 +2045,37 @@ static void
 nv50_head_atomic_check_mode(struct nv50_head *head, struct nv50_head_atom *asyh)
 {
 	struct drm_display_mode *mode = &asyh->state.adjusted_mode;
-	u32 ilace   = (mode->flags & DRM_MODE_FLAG_INTERLACE) ? 2 : 1;
-	u32 vscan   = (mode->flags & DRM_MODE_FLAG_DBLSCAN) ? 2 : 1;
-	u32 hbackp  =  mode->htotal - mode->hsync_end;
-	u32 vbackp  = (mode->vtotal - mode->vsync_end) * vscan / ilace;
-	u32 hfrontp =  mode->hsync_start - mode->hdisplay;
-	u32 vfrontp = (mode->vsync_start - mode->vdisplay) * vscan / ilace;
-	u32 blankus;
 	struct nv50_head_mode *m = &asyh->mode;
+	u32 blankus;
 
-	m->h.active = mode->htotal;
-	m->h.synce  = mode->hsync_end - mode->hsync_start - 1;
-	m->h.blanke = m->h.synce + hbackp;
-	m->h.blanks = mode->htotal - hfrontp - 1;
+	drm_mode_set_crtcinfo(mode, CRTC_INTERLACE_HALVE_V | CRTC_STEREO_DOUBLE);
 
-	m->v.active = mode->vtotal * vscan / ilace;
-	m->v.synce  = ((mode->vsync_end - mode->vsync_start) * vscan / ilace) - 1;
-	m->v.blanke = m->v.synce + vbackp;
-	m->v.blanks = m->v.active - vfrontp - 1;
+	/*
+	 * DRM modes are defined in terms of a repeating interval
+	 * starting with the active display area.  The hardware modes
+	 * are defined in terms of a repeating interval starting one
+	 * unit (pixel or line) into the sync pulse.  So, add bias.
+	 */
+
+	m->h.active = mode->crtc_htotal;
+	m->h.synce  = mode->crtc_hsync_end - mode->crtc_hsync_start - 1;
+	m->h.blanke = mode->crtc_hblank_end - mode->crtc_hsync_start - 1;
+	m->h.blanks = m->h.blanke + mode->crtc_hdisplay;
+
+	m->v.active = mode->crtc_vtotal;
+	m->v.synce  = mode->crtc_vsync_end - mode->crtc_vsync_start - 1;
+	m->v.blanke = mode->crtc_vblank_end - mode->crtc_vsync_start - 1;
+	m->v.blanks = m->v.blanke + mode->crtc_vdisplay;
 
 	/*XXX: Safe underestimate, even "0" works */
-	blankus = (m->v.active - mode->vdisplay - 2) * m->h.active;
+	blankus = (m->v.active - mode->crtc_vdisplay - 2) * m->h.active;
 	blankus *= 1000;
-	blankus /= mode->clock;
+	blankus /= mode->crtc_clock;
 	m->v.blankus = blankus;
 
 	if (mode->flags & DRM_MODE_FLAG_INTERLACE) {
-		m->v.blank2e =  m->v.active + m->v.synce + vbackp;
-		m->v.blank2s =  m->v.blank2e + (mode->vdisplay * vscan / ilace);
+		m->v.blank2e =  m->v.active + m->v.blanke;
+		m->v.blank2s =  m->v.blank2e + mode->crtc_vdisplay;
 		m->v.active  = (m->v.active * 2) + 1;
 		m->interlace = true;
 	} else {
@@ -2071,9 +2083,8 @@ nv50_head_atomic_check_mode(struct nv50_head *head, struct nv50_head_atom *asyh)
 		m->v.blank2s = 1;
 		m->interlace = false;
 	}
-	m->clock = mode->clock;
+	m->clock = mode->crtc_clock;
 
-	drm_mode_set_crtcinfo(mode, CRTC_INTERLACE_HALVE_V);
 	asyh->set.mode = true;
 }
 
@@ -2392,6 +2403,51 @@ out:
 /******************************************************************************
  * 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
 nv50_outp_atomic_check_view(struct drm_encoder *encoder,
 			    struct drm_crtc_state *crtc_state,
@@ -2449,30 +2505,6 @@ nv50_outp_atomic_check(struct drm_encoder *encoder,
  * DAC
  *****************************************************************************/
 static void
-nv50_dac_dpms(struct drm_encoder *encoder, int mode)
-{
-	struct nouveau_encoder *nv_encoder = nouveau_encoder(encoder);
-	struct nv50_disp *disp = nv50_disp(encoder->dev);
-	struct {
-		struct nv50_disp_mthd_v1 base;
-		struct nv50_disp_dac_pwr_v0 pwr;
-	} args = {
-		.base.version = 1,
-		.base.method = NV50_DISP_MTHD_V1_DAC_PWR,
-		.base.hasht  = nv_encoder->dcb->hasht,
-		.base.hashm  = nv_encoder->dcb->hashm,
-		.pwr.state = 1,
-		.pwr.data  = 1,
-		.pwr.vsync = (mode != DRM_MODE_DPMS_SUSPEND &&
-			      mode != DRM_MODE_DPMS_OFF),
-		.pwr.hsync = (mode != DRM_MODE_DPMS_STANDBY &&
-			      mode != DRM_MODE_DPMS_OFF),
-	};
-
-	nvif_mthd(disp->disp, 0, &args, sizeof(args));
-}
-
-static void
 nv50_dac_disable(struct drm_encoder *encoder)
 {
 	struct nouveau_encoder *nv_encoder = nouveau_encoder(encoder);
@@ -2495,6 +2527,7 @@ nv50_dac_disable(struct drm_encoder *encoder)
 	}
 
 	nv_encoder->crtc = NULL;
+	nv50_outp_release(nv_encoder);
 }
 
 static void
@@ -2506,6 +2539,8 @@ nv50_dac_enable(struct drm_encoder *encoder)
 	struct drm_display_mode *mode = &nv_crtc->base.state->adjusted_mode;
 	u32 *push;
 
+	nv50_outp_acquire(nv_encoder);
+
 	push = evo_wait(mast, 8);
 	if (push) {
 		if (nv50_vers(mast) < GF110_DISP_CORE_CHANNEL_DMA) {
@@ -2573,7 +2608,6 @@ nv50_dac_detect(struct drm_encoder *encoder, struct drm_connector *connector)
 
 static const struct drm_encoder_helper_funcs
 nv50_dac_help = {
-	.dpms = nv50_dac_dpms,
 	.atomic_check = nv50_outp_atomic_check,
 	.enable = nv50_dac_enable,
 	.disable = nv50_dac_disable,
@@ -2606,7 +2640,6 @@ nv50_dac_create(struct drm_connector *connector, struct dcb_output *dcbe)
 	if (!nv_encoder)
 		return -ENOMEM;
 	nv_encoder->dcb = dcbe;
-	nv_encoder->or = ffs(dcbe->or) - 1;
 
 	bus = nvkm_i2c_bus_find(i2c, dcbe->i2c_index);
 	if (bus)
@@ -2708,6 +2741,7 @@ nv50_hdmi_enable(struct drm_encoder *encoder, struct drm_display_mode *mode)
 	struct {
 		struct nv50_disp_mthd_v1 base;
 		struct nv50_disp_sor_hdmi_pwr_v0 pwr;
+		u8 infoframes[2 * 17]; /* two frames, up to 17 bytes each */
 	} args = {
 		.base.version = 1,
 		.base.method = NV50_DISP_MTHD_V1_SOR_HDMI_PWR,
@@ -2719,17 +2753,42 @@ nv50_hdmi_enable(struct drm_encoder *encoder, struct drm_display_mode *mode)
 	};
 	struct nouveau_connector *nv_connector;
 	u32 max_ac_packet;
+	union hdmi_infoframe avi_frame;
+	union hdmi_infoframe vendor_frame;
+	int ret;
+	int size;
 
 	nv_connector = nouveau_encoder_connector_get(nv_encoder);
 	if (!drm_detect_hdmi_monitor(nv_connector->edid))
 		return;
 
+	ret = drm_hdmi_avi_infoframe_from_display_mode(&avi_frame.avi, mode);
+	if (!ret) {
+		/* We have an AVI InfoFrame, populate it to the display */
+		args.pwr.avi_infoframe_length
+			= hdmi_infoframe_pack(&avi_frame, args.infoframes, 17);
+	}
+
+	ret = drm_hdmi_vendor_infoframe_from_display_mode(&vendor_frame.vendor.hdmi, mode);
+	if (!ret) {
+		/* We have a Vendor InfoFrame, populate it to the display */
+		args.pwr.vendor_infoframe_length
+			= hdmi_infoframe_pack(&vendor_frame,
+					      args.infoframes
+					      + args.pwr.avi_infoframe_length,
+					      17);
+	}
+
 	max_ac_packet  = mode->htotal - mode->hdisplay;
 	max_ac_packet -= args.pwr.rekey;
 	max_ac_packet -= 18; /* constant from tegra */
 	args.pwr.max_ac_packet = max_ac_packet / 32;
 
-	nvif_mthd(disp->disp, 0, &args, sizeof(args));
+	size = sizeof(args.base)
+		+ sizeof(args.pwr)
+		+ args.pwr.avi_infoframe_length
+		+ args.pwr.vendor_infoframe_length;
+	nvif_mthd(disp->disp, 0, &args, size);
 	nv50_audio_enable(encoder, mode);
 }
 
@@ -2747,6 +2806,8 @@ struct nv50_mstm {
 	struct nv50_msto *msto[4];
 
 	bool modified;
+	bool disabled;
+	int links;
 };
 
 struct nv50_mstc {
@@ -2873,17 +2934,20 @@ nv50_msto_enable(struct drm_encoder *encoder)
 	struct nv50_mstc *mstc = NULL;
 	struct nv50_mstm *mstm = NULL;
 	struct drm_connector *connector;
+	struct drm_connector_list_iter conn_iter;
 	u8 proto, depth;
 	int slots;
 	bool r;
 
-	drm_for_each_connector(connector, encoder->dev) {
+	drm_connector_list_iter_begin(encoder->dev, &conn_iter);
+	drm_for_each_connector_iter(connector, &conn_iter) {
 		if (connector->state->best_encoder == &msto->encoder) {
 			mstc = nv50_mstc(connector);
 			mstm = mstc->mstm;
 			break;
 		}
 	}
+	drm_connector_list_iter_end(&conn_iter);
 
 	if (WARN_ON(!mstc))
 		return;
@@ -2892,7 +2956,10 @@ nv50_msto_enable(struct drm_encoder *encoder)
 	r = drm_dp_mst_allocate_vcpi(&mstm->mgr, mstc->port, mstc->pbn, slots);
 	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;
 	else
 		proto = 0x9;
@@ -2924,6 +2991,8 @@ nv50_msto_disable(struct drm_encoder *encoder)
 
 	mstm->outp->update(mstm->outp, msto->head->base.index, NULL, 0, 0);
 	mstm->modified = true;
+	if (!--mstm->links)
+		mstm->disabled = true;
 	msto->disabled = true;
 }
 
@@ -3139,6 +3208,12 @@ nv50_mstm_prepare(struct nv50_mstm *mstm)
 				nv50_msto_prepare(msto);
 		}
 	}
+
+	if (mstm->disabled) {
+		if (!mstm->links)
+			nv50_outp_release(mstm->outp);
+		mstm->disabled = false;
+	}
 }
 
 static void
@@ -3366,25 +3441,6 @@ nv50_mstm_new(struct nouveau_encoder *outp, struct drm_dp_aux *aux, int aux_max,
  * SOR
  *****************************************************************************/
 static void
-nv50_sor_dpms(struct drm_encoder *encoder, int mode)
-{
-	struct nouveau_encoder *nv_encoder = nouveau_encoder(encoder);
-	struct nv50_disp *disp = nv50_disp(encoder->dev);
-	struct {
-		struct nv50_disp_mthd_v1 base;
-		struct nv50_disp_sor_pwr_v0 pwr;
-	} args = {
-		.base.version = 1,
-		.base.method = NV50_DISP_MTHD_V1_SOR_PWR,
-		.base.hasht  = nv_encoder->dcb->hasht,
-		.base.hashm  = nv_encoder->dcb->hashm,
-		.pwr.state = mode == DRM_MODE_DPMS_ON,
-	};
-
-	nvif_mthd(disp->disp, 0, &args, sizeof(args));
-}
-
-static void
 nv50_sor_update(struct nouveau_encoder *nv_encoder, u8 head,
 		struct drm_display_mode *mode, u8 proto, u8 depth)
 {
@@ -3456,6 +3512,7 @@ nv50_sor_disable(struct drm_encoder *encoder)
 		nv_encoder->update(nv_encoder, nv_crtc->index, NULL, 0, 0);
 		nv50_audio_disable(encoder, nv_crtc);
 		nv50_hdmi_disable(&nv_encoder->base.base, nv_crtc);
+		nv50_outp_release(nv_encoder);
 	}
 }
 
@@ -3484,10 +3541,11 @@ nv50_sor_enable(struct drm_encoder *encoder)
 
 	nv_connector = nouveau_encoder_connector_get(nv_encoder);
 	nv_encoder->crtc = encoder->crtc;
+	nv50_outp_acquire(nv_encoder);
 
 	switch (nv_encoder->dcb->type) {
 	case DCB_OUTPUT_TMDS:
-		if (nv_encoder->dcb->sorconf.link & 1) {
+		if (nv_encoder->link & 1) {
 			proto = 0x1;
 			/* Only enable dual-link if:
 			 *  - Need to (i.e. rate > 165MHz)
@@ -3545,7 +3603,7 @@ nv50_sor_enable(struct drm_encoder *encoder)
 		else
 			depth = 0x6;
 
-		if (nv_encoder->dcb->sorconf.link & 1)
+		if (nv_encoder->link & 1)
 			proto = 0x8;
 		else
 			proto = 0x9;
@@ -3562,7 +3620,6 @@ nv50_sor_enable(struct drm_encoder *encoder)
 
 static const struct drm_encoder_helper_funcs
 nv50_sor_help = {
-	.dpms = nv50_sor_dpms,
 	.atomic_check = nv50_outp_atomic_check,
 	.enable = nv50_sor_enable,
 	.disable = nv50_sor_disable,
@@ -3605,7 +3662,6 @@ nv50_sor_create(struct drm_connector *connector, struct dcb_output *dcbe)
 	if (!nv_encoder)
 		return -ENOMEM;
 	nv_encoder->dcb = dcbe;
-	nv_encoder->or = ffs(dcbe->or) - 1;
 	nv_encoder->update = nv50_sor_update;
 
 	encoder = to_drm_encoder(nv_encoder);
@@ -3646,26 +3702,6 @@ nv50_sor_create(struct drm_connector *connector, struct dcb_output *dcbe)
 /******************************************************************************
  * PIOR
  *****************************************************************************/
-static void
-nv50_pior_dpms(struct drm_encoder *encoder, int mode)
-{
-	struct nouveau_encoder *nv_encoder = nouveau_encoder(encoder);
-	struct nv50_disp *disp = nv50_disp(encoder->dev);
-	struct {
-		struct nv50_disp_mthd_v1 base;
-		struct nv50_disp_pior_pwr_v0 pwr;
-	} args = {
-		.base.version = 1,
-		.base.method = NV50_DISP_MTHD_V1_PIOR_PWR,
-		.base.hasht  = nv_encoder->dcb->hasht,
-		.base.hashm  = nv_encoder->dcb->hashm,
-		.pwr.state = mode == DRM_MODE_DPMS_ON,
-		.pwr.type = nv_encoder->dcb->type,
-	};
-
-	nvif_mthd(disp->disp, 0, &args, sizeof(args));
-}
-
 static int
 nv50_pior_atomic_check(struct drm_encoder *encoder,
 		       struct drm_crtc_state *crtc_state,
@@ -3698,6 +3734,7 @@ nv50_pior_disable(struct drm_encoder *encoder)
 	}
 
 	nv_encoder->crtc = NULL;
+	nv50_outp_release(nv_encoder);
 }
 
 static void
@@ -3712,6 +3749,8 @@ nv50_pior_enable(struct drm_encoder *encoder)
 	u8 proto, depth;
 	u32 *push;
 
+	nv50_outp_acquire(nv_encoder);
+
 	nv_connector = nouveau_encoder_connector_get(nv_encoder);
 	switch (nv_connector->base.display_info.bpc) {
 	case 10: depth = 0x6; break;
@@ -3750,7 +3789,6 @@ nv50_pior_enable(struct drm_encoder *encoder)
 
 static const struct drm_encoder_helper_funcs
 nv50_pior_help = {
-	.dpms = nv50_pior_dpms,
 	.atomic_check = nv50_pior_atomic_check,
 	.enable = nv50_pior_enable,
 	.disable = nv50_pior_disable,
@@ -3800,7 +3838,6 @@ nv50_pior_create(struct drm_connector *connector, struct dcb_output *dcbe)
 	if (!nv_encoder)
 		return -ENOMEM;
 	nv_encoder->dcb = dcbe;
-	nv_encoder->or = ffs(dcbe->or) - 1;
 	nv_encoder->i2c = ddc;
 	nv_encoder->aux = aux;
 
@@ -4315,14 +4352,8 @@ nv50_display_init(struct drm_device *dev)
 
 	list_for_each_entry(encoder, &dev->mode_config.encoder_list, head) {
 		if (encoder->encoder_type != DRM_MODE_ENCODER_DPMST) {
-			const struct drm_encoder_helper_funcs *help;
-			struct nouveau_encoder *nv_encoder;
-
-			nv_encoder = nouveau_encoder(encoder);
-			help = encoder->helper_private;
-			if (help && help->dpms)
-				help->dpms(encoder, DRM_MODE_DPMS_ON);
-
+			struct nouveau_encoder *nv_encoder =
+				nouveau_encoder(encoder);
 			nv50_mstm_init(nv_encoder->dp.mstm);
 		}
 	}
diff --git a/drivers/gpu/drm/nouveau/nvkm/engine/device/base.c b/drivers/gpu/drm/nouveau/nvkm/engine/device/base.c
index b690bc12a5b7..7bdc7a5ae723 100644
--- a/drivers/gpu/drm/nouveau/nvkm/engine/device/base.c
+++ b/drivers/gpu/drm/nouveau/nvkm/engine/device/base.c
@@ -1257,7 +1257,7 @@ nvaa_chipset = {
 	.therm = g84_therm_new,
 	.timer = nv41_timer_new,
 	.volt = nv40_volt_new,
-	.disp = g94_disp_new,
+	.disp = mcp77_disp_new,
 	.dma = nv50_dma_new,
 	.fifo = g84_fifo_new,
 	.gr = gt200_gr_new,
@@ -1289,7 +1289,7 @@ nvac_chipset = {
 	.therm = g84_therm_new,
 	.timer = nv41_timer_new,
 	.volt = nv40_volt_new,
-	.disp = g94_disp_new,
+	.disp = mcp77_disp_new,
 	.dma = nv50_dma_new,
 	.fifo = g84_fifo_new,
 	.gr = mcp79_gr_new,
@@ -1323,7 +1323,7 @@ nvaf_chipset = {
 	.timer = nv41_timer_new,
 	.volt = nv40_volt_new,
 	.ce[0] = gt215_ce_new,
-	.disp = gt215_disp_new,
+	.disp = mcp89_disp_new,
 	.dma = nv50_dma_new,
 	.fifo = g84_fifo_new,
 	.gr = mcp89_gr_new,
diff --git a/drivers/gpu/drm/nouveau/nvkm/engine/device/tegra.c b/drivers/gpu/drm/nouveau/nvkm/engine/device/tegra.c
index 6474bd2a6d07..189ed80e21ff 100644
--- a/drivers/gpu/drm/nouveau/nvkm/engine/device/tegra.c
+++ b/drivers/gpu/drm/nouveau/nvkm/engine/device/tegra.c
@@ -51,10 +51,12 @@ nvkm_device_tegra_power_up(struct nvkm_device_tegra *tdev)
 	reset_control_assert(tdev->rst);
 	udelay(10);
 
-	ret = tegra_powergate_remove_clamping(TEGRA_POWERGATE_3D);
-	if (ret)
-		goto err_clamp;
-	udelay(10);
+	if (!tdev->pdev->dev.pm_domain) {
+		ret = tegra_powergate_remove_clamping(TEGRA_POWERGATE_3D);
+		if (ret)
+			goto err_clamp;
+		udelay(10);
+	}
 
 	reset_control_deassert(tdev->rst);
 	udelay(10);
@@ -80,9 +82,6 @@ nvkm_device_tegra_power_down(struct nvkm_device_tegra *tdev)
 {
 	int ret;
 
-	reset_control_assert(tdev->rst);
-	udelay(10);
-
 	clk_disable_unprepare(tdev->clk_pwr);
 	if (tdev->clk_ref)
 		clk_disable_unprepare(tdev->clk_ref);
diff --git a/drivers/gpu/drm/nouveau/nvkm/engine/disp/Kbuild b/drivers/gpu/drm/nouveau/nvkm/engine/disp/Kbuild
index fa05d16ae948..48ce6699183e 100644
--- a/drivers/gpu/drm/nouveau/nvkm/engine/disp/Kbuild
+++ b/drivers/gpu/drm/nouveau/nvkm/engine/disp/Kbuild
@@ -4,7 +4,9 @@ nvkm-y += nvkm/engine/disp/nv50.o
 nvkm-y += nvkm/engine/disp/g84.o
 nvkm-y += nvkm/engine/disp/g94.o
 nvkm-y += nvkm/engine/disp/gt200.o
+nvkm-y += nvkm/engine/disp/mcp77.o
 nvkm-y += nvkm/engine/disp/gt215.o
+nvkm-y += nvkm/engine/disp/mcp89.o
 nvkm-y += nvkm/engine/disp/gf119.o
 nvkm-y += nvkm/engine/disp/gk104.o
 nvkm-y += nvkm/engine/disp/gk110.o
@@ -12,29 +14,41 @@ nvkm-y += nvkm/engine/disp/gm107.o
 nvkm-y += nvkm/engine/disp/gm200.o
 nvkm-y += nvkm/engine/disp/gp100.o
 nvkm-y += nvkm/engine/disp/gp102.o
+nvkm-y += nvkm/engine/disp/vga.o
 
-nvkm-y += nvkm/engine/disp/outp.o
-nvkm-y += nvkm/engine/disp/outpdp.o
+nvkm-y += nvkm/engine/disp/head.o
+nvkm-y += nvkm/engine/disp/headnv04.o
+nvkm-y += nvkm/engine/disp/headnv50.o
+nvkm-y += nvkm/engine/disp/headgf119.o
+
+nvkm-y += nvkm/engine/disp/ior.o
 nvkm-y += nvkm/engine/disp/dacnv50.o
+nvkm-y += nvkm/engine/disp/dacgf119.o
 nvkm-y += nvkm/engine/disp/piornv50.o
 nvkm-y += nvkm/engine/disp/sornv50.o
+nvkm-y += nvkm/engine/disp/sorg84.o
 nvkm-y += nvkm/engine/disp/sorg94.o
+nvkm-y += nvkm/engine/disp/sormcp77.o
+nvkm-y += nvkm/engine/disp/sorgt215.o
+nvkm-y += nvkm/engine/disp/sormcp89.o
 nvkm-y += nvkm/engine/disp/sorgf119.o
+nvkm-y += nvkm/engine/disp/sorgk104.o
 nvkm-y += nvkm/engine/disp/sorgm107.o
 nvkm-y += nvkm/engine/disp/sorgm200.o
-nvkm-y += nvkm/engine/disp/dport.o
 
-nvkm-y += nvkm/engine/disp/conn.o
+nvkm-y += nvkm/engine/disp/outp.o
+nvkm-y += nvkm/engine/disp/dp.o
 
 nvkm-y += nvkm/engine/disp/hdagt215.o
 nvkm-y += nvkm/engine/disp/hdagf119.o
 
+nvkm-y += nvkm/engine/disp/hdmi.o
 nvkm-y += nvkm/engine/disp/hdmig84.o
 nvkm-y += nvkm/engine/disp/hdmigt215.o
 nvkm-y += nvkm/engine/disp/hdmigf119.o
 nvkm-y += nvkm/engine/disp/hdmigk104.o
 
-nvkm-y += nvkm/engine/disp/vga.o
+nvkm-y += nvkm/engine/disp/conn.o
 
 nvkm-y += nvkm/engine/disp/rootnv04.o
 nvkm-y += nvkm/engine/disp/rootnv50.o
diff --git a/drivers/gpu/drm/nouveau/nvkm/engine/disp/base.c b/drivers/gpu/drm/nouveau/nvkm/engine/disp/base.c
index 1efe91b1e22b..c7c84d34d97e 100644
--- a/drivers/gpu/drm/nouveau/nvkm/engine/disp/base.c
+++ b/drivers/gpu/drm/nouveau/nvkm/engine/disp/base.c
@@ -23,6 +23,9 @@
  */
 #include "priv.h"
 #include "conn.h"
+#include "dp.h"
+#include "head.h"
+#include "ior.h"
 #include "outp.h"
 
 #include <core/client.h>
@@ -37,17 +40,21 @@
 #include <nvif/unpack.h>
 
 static void
-nvkm_disp_vblank_fini(struct nvkm_event *event, int type, int head)
+nvkm_disp_vblank_fini(struct nvkm_event *event, int type, int id)
 {
 	struct nvkm_disp *disp = container_of(event, typeof(*disp), vblank);
-	disp->func->head.vblank_fini(disp, head);
+	struct nvkm_head *head = nvkm_head_find(disp, id);
+	if (head)
+		head->func->vblank_put(head);
 }
 
 static void
-nvkm_disp_vblank_init(struct nvkm_event *event, int type, int head)
+nvkm_disp_vblank_init(struct nvkm_event *event, int type, int id)
 {
 	struct nvkm_disp *disp = container_of(event, typeof(*disp), vblank);
-	disp->func->head.vblank_init(disp, head);
+	struct nvkm_head *head = nvkm_head_find(disp, id);
+	if (head)
+		head->func->vblank_get(head);
 }
 
 static int
@@ -96,7 +103,7 @@ nvkm_disp_hpd_ctor(struct nvkm_object *object, void *data, u32 size,
 	union {
 		struct nvif_notify_conn_req_v0 v0;
 	} *req = data;
-	struct nvkm_output *outp;
+	struct nvkm_outp *outp;
 	int ret = -ENOSYS;
 
 	if (!(ret = nvif_unpack(ret, &data, &size, req->v0, 0, 0, false))) {
@@ -210,15 +217,15 @@ static int
 nvkm_disp_fini(struct nvkm_engine *engine, bool suspend)
 {
 	struct nvkm_disp *disp = nvkm_disp(engine);
-	struct nvkm_connector *conn;
-	struct nvkm_output *outp;
+	struct nvkm_conn *conn;
+	struct nvkm_outp *outp;
 
 	list_for_each_entry(outp, &disp->outp, head) {
-		nvkm_output_fini(outp);
+		nvkm_outp_fini(outp);
 	}
 
 	list_for_each_entry(conn, &disp->conn, head) {
-		nvkm_connector_fini(conn);
+		nvkm_conn_fini(conn);
 	}
 
 	return 0;
@@ -228,129 +235,70 @@ static int
 nvkm_disp_init(struct nvkm_engine *engine)
 {
 	struct nvkm_disp *disp = nvkm_disp(engine);
-	struct nvkm_connector *conn;
-	struct nvkm_output *outp;
+	struct nvkm_conn *conn;
+	struct nvkm_outp *outp;
 
 	list_for_each_entry(conn, &disp->conn, head) {
-		nvkm_connector_init(conn);
+		nvkm_conn_init(conn);
 	}
 
 	list_for_each_entry(outp, &disp->outp, head) {
-		nvkm_output_init(outp);
+		nvkm_outp_init(outp);
 	}
 
 	return 0;
 }
 
-static void *
-nvkm_disp_dtor(struct nvkm_engine *engine)
+static int
+nvkm_disp_oneinit(struct nvkm_engine *engine)
 {
 	struct nvkm_disp *disp = nvkm_disp(engine);
-	struct nvkm_connector *conn;
-	struct nvkm_output *outp;
-	void *data = disp;
-
-	if (disp->func->dtor)
-		data = disp->func->dtor(disp);
-
-	nvkm_event_fini(&disp->vblank);
-	nvkm_event_fini(&disp->hpd);
-
-	while (!list_empty(&disp->outp)) {
-		outp = list_first_entry(&disp->outp, typeof(*outp), head);
-		list_del(&outp->head);
-		nvkm_output_del(&outp);
-	}
-
-	while (!list_empty(&disp->conn)) {
-		conn = list_first_entry(&disp->conn, typeof(*conn), head);
-		list_del(&conn->head);
-		nvkm_connector_del(&conn);
-	}
-
-	return data;
-}
-
-static const struct nvkm_engine_func
-nvkm_disp = {
-	.dtor = nvkm_disp_dtor,
-	.init = nvkm_disp_init,
-	.fini = nvkm_disp_fini,
-	.intr = nvkm_disp_intr,
-	.base.sclass = nvkm_disp_class_get,
-};
-
-int
-nvkm_disp_ctor(const struct nvkm_disp_func *func, struct nvkm_device *device,
-	       int index, int heads, struct nvkm_disp *disp)
-{
-	struct nvkm_bios *bios = device->bios;
-	struct nvkm_output *outp, *outt, *pair;
-	struct nvkm_connector *conn;
+	struct nvkm_subdev *subdev = &disp->engine.subdev;
+	struct nvkm_bios *bios = subdev->device->bios;
+	struct nvkm_outp *outp, *outt, *pair;
+	struct nvkm_conn *conn;
+	struct nvkm_head *head;
 	struct nvbios_connE connE;
 	struct dcb_output dcbE;
 	u8  hpd = 0, ver, hdr;
 	u32 data;
 	int ret, i;
 
-	INIT_LIST_HEAD(&disp->outp);
-	INIT_LIST_HEAD(&disp->conn);
-	disp->func = func;
-	disp->head.nr = heads;
-
-	ret = nvkm_engine_ctor(&nvkm_disp, device, index, true, &disp->engine);
-	if (ret)
-		return ret;
-
-	/* create output objects for each display path in the vbios */
+	/* Create output path objects for each VBIOS display path. */
 	i = -1;
 	while ((data = dcb_outp_parse(bios, ++i, &ver, &hdr, &dcbE))) {
-		const struct nvkm_disp_func_outp *outps;
-		int (*ctor)(struct nvkm_disp *, int, struct dcb_output *,
-			    struct nvkm_output **);
-
 		if (dcbE.type == DCB_OUTPUT_UNUSED)
 			continue;
 		if (dcbE.type == DCB_OUTPUT_EOL)
 			break;
 		outp = NULL;
 
-		switch (dcbE.location) {
-		case 0: outps = &disp->func->outp.internal; break;
-		case 1: outps = &disp->func->outp.external; break;
-		default:
-			nvkm_warn(&disp->engine.subdev,
-				  "dcb %d locn %d unknown\n", i, dcbE.location);
-			continue;
-		}
-
 		switch (dcbE.type) {
-		case DCB_OUTPUT_ANALOG: ctor = outps->crt ; break;
-		case DCB_OUTPUT_TV    : ctor = outps->tv  ; break;
-		case DCB_OUTPUT_TMDS  : ctor = outps->tmds; break;
-		case DCB_OUTPUT_LVDS  : ctor = outps->lvds; break;
-		case DCB_OUTPUT_DP    : ctor = outps->dp  ; break;
+		case DCB_OUTPUT_ANALOG:
+		case DCB_OUTPUT_TV:
+		case DCB_OUTPUT_TMDS:
+		case DCB_OUTPUT_LVDS:
+			ret = nvkm_outp_new(disp, i, &dcbE, &outp);
+			break;
+		case DCB_OUTPUT_DP:
+			ret = nvkm_dp_new(disp, i, &dcbE, &outp);
+			break;
 		default:
-			nvkm_warn(&disp->engine.subdev,
-				  "dcb %d type %d unknown\n", i, dcbE.type);
+			nvkm_warn(subdev, "dcb %d type %d unknown\n",
+				  i, dcbE.type);
 			continue;
 		}
 
-		if (ctor)
-			ret = ctor(disp, i, &dcbE, &outp);
-		else
-			ret = -ENODEV;
-
 		if (ret) {
-			if (ret == -ENODEV) {
-				nvkm_debug(&disp->engine.subdev,
-					   "dcb %d %d/%d not supported\n",
-					   i, dcbE.location, dcbE.type);
+			if (outp) {
+				if (ret != -ENODEV)
+					OUTP_ERR(outp, "ctor failed: %d", ret);
+				else
+					OUTP_DBG(outp, "not supported");
+				nvkm_outp_del(&outp);
 				continue;
 			}
-			nvkm_error(&disp->engine.subdev,
-				   "failed to create output %d\n", i);
-			nvkm_output_del(&outp);
+			nvkm_error(subdev, "failed to create outp %d\n", i);
 			continue;
 		}
 
@@ -358,18 +306,18 @@ nvkm_disp_ctor(const struct nvkm_disp_func *func, struct nvkm_device *device,
 		hpd = max(hpd, (u8)(dcbE.connector + 1));
 	}
 
-	/* create connector objects based on the outputs we support */
+	/* Create connector objects based on available output paths. */
 	list_for_each_entry_safe(outp, outt, &disp->outp, head) {
-		/* bios data *should* give us the most useful information */
+		/* VBIOS data *should* give us the most useful information. */
 		data = nvbios_connEp(bios, outp->info.connector, &ver, &hdr,
 				     &connE);
 
-		/* no bios connector data... */
+		/* No bios connector data... */
 		if (!data) {
-			/* heuristic: anything with the same ccb index is
+			/* Heuristic: anything with the same ccb index is
 			 * considered to be on the same connector, any
 			 * output path without an associated ccb entry will
-			 * be put on its own connector
+			 * be put on its own connector.
 			 */
 			int ccb_index = outp->info.i2c_index;
 			if (ccb_index != 0xf) {
@@ -381,7 +329,7 @@ nvkm_disp_ctor(const struct nvkm_disp_func *func, struct nvkm_device *device,
 				}
 			}
 
-			/* connector shared with another output path */
+			/* Connector shared with another output path. */
 			if (outp->conn)
 				continue;
 
@@ -392,7 +340,7 @@ nvkm_disp_ctor(const struct nvkm_disp_func *func, struct nvkm_device *device,
 			i = outp->info.connector;
 		}
 
-		/* check that we haven't already created this connector */
+		/* Check that we haven't already created this connector. */
 		list_for_each_entry(conn, &disp->conn, head) {
 			if (conn->index == outp->info.connector) {
 				outp->conn = conn;
@@ -403,15 +351,15 @@ nvkm_disp_ctor(const struct nvkm_disp_func *func, struct nvkm_device *device,
 		if (outp->conn)
 			continue;
 
-		/* apparently we need to create a new one! */
-		ret = nvkm_connector_new(disp, i, &connE, &outp->conn);
+		/* Apparently we need to create a new one! */
+		ret = nvkm_conn_new(disp, i, &connE, &outp->conn);
 		if (ret) {
 			nvkm_error(&disp->engine.subdev,
-				   "failed to create output %d conn: %d\n",
+				   "failed to create outp %d conn: %d\n",
 				   outp->index, ret);
-			nvkm_connector_del(&outp->conn);
+			nvkm_conn_del(&outp->conn);
 			list_del(&outp->head);
-			nvkm_output_del(&outp);
+			nvkm_outp_del(&outp);
 			continue;
 		}
 
@@ -422,18 +370,81 @@ nvkm_disp_ctor(const struct nvkm_disp_func *func, struct nvkm_device *device,
 	if (ret)
 		return ret;
 
-	ret = nvkm_event_init(&nvkm_disp_vblank_func, 1, heads, &disp->vblank);
-	if (ret)
-		return ret;
+	i = 0;
+	list_for_each_entry(head, &disp->head, head)
+		i = max(i, head->id + 1);
 
-	return 0;
+	return nvkm_event_init(&nvkm_disp_vblank_func, 1, i, &disp->vblank);
+}
+
+static void *
+nvkm_disp_dtor(struct nvkm_engine *engine)
+{
+	struct nvkm_disp *disp = nvkm_disp(engine);
+	struct nvkm_conn *conn;
+	struct nvkm_outp *outp;
+	void *data = disp;
+
+	if (disp->func->dtor)
+		data = disp->func->dtor(disp);
+
+	nvkm_event_fini(&disp->vblank);
+	nvkm_event_fini(&disp->hpd);
+
+	while (!list_empty(&disp->conn)) {
+		conn = list_first_entry(&disp->conn, typeof(*conn), head);
+		list_del(&conn->head);
+		nvkm_conn_del(&conn);
+	}
+
+	while (!list_empty(&disp->outp)) {
+		outp = list_first_entry(&disp->outp, typeof(*outp), head);
+		list_del(&outp->head);
+		nvkm_outp_del(&outp);
+	}
+
+	while (!list_empty(&disp->ior)) {
+		struct nvkm_ior *ior =
+			list_first_entry(&disp->ior, typeof(*ior), head);
+		nvkm_ior_del(&ior);
+	}
+
+	while (!list_empty(&disp->head)) {
+		struct nvkm_head *head =
+			list_first_entry(&disp->head, typeof(*head), head);
+		nvkm_head_del(&head);
+	}
+
+	return data;
+}
+
+static const struct nvkm_engine_func
+nvkm_disp = {
+	.dtor = nvkm_disp_dtor,
+	.oneinit = nvkm_disp_oneinit,
+	.init = nvkm_disp_init,
+	.fini = nvkm_disp_fini,
+	.intr = nvkm_disp_intr,
+	.base.sclass = nvkm_disp_class_get,
+};
+
+int
+nvkm_disp_ctor(const struct nvkm_disp_func *func, struct nvkm_device *device,
+	       int index, struct nvkm_disp *disp)
+{
+	disp->func = func;
+	INIT_LIST_HEAD(&disp->head);
+	INIT_LIST_HEAD(&disp->ior);
+	INIT_LIST_HEAD(&disp->outp);
+	INIT_LIST_HEAD(&disp->conn);
+	return nvkm_engine_ctor(&nvkm_disp, device, index, true, &disp->engine);
 }
 
 int
 nvkm_disp_new_(const struct nvkm_disp_func *func, struct nvkm_device *device,
-	       int index, int heads, struct nvkm_disp **pdisp)
+	       int index, struct nvkm_disp **pdisp)
 {
 	if (!(*pdisp = kzalloc(sizeof(**pdisp), GFP_KERNEL)))
 		return -ENOMEM;
-	return nvkm_disp_ctor(func, device, index, heads, *pdisp);
+	return nvkm_disp_ctor(func, device, index, *pdisp);
 }
diff --git a/drivers/gpu/drm/nouveau/nvkm/engine/disp/basenv50.c b/drivers/gpu/drm/nouveau/nvkm/engine/disp/basenv50.c
index 83f152300ec0..f1d6b820d482 100644
--- a/drivers/gpu/drm/nouveau/nvkm/engine/disp/basenv50.c
+++ b/drivers/gpu/drm/nouveau/nvkm/engine/disp/basenv50.c
@@ -22,6 +22,7 @@
  * Authors: Ben Skeggs
  */
 #include "dmacnv50.h"
+#include "head.h"
 #include "rootnv50.h"
 
 #include <core/client.h>
@@ -50,7 +51,7 @@ nv50_disp_base_new(const struct nv50_disp_dmac_func *func,
 		nvif_ioctl(parent, "create disp base channel dma vers %d "
 				   "pushbuf %016llx head %d\n",
 			   args->v0.version, args->v0.pushbuf, args->v0.head);
-		if (args->v0.head > disp->base.head.nr)
+		if (!nvkm_head_find(&disp->base, args->v0.head))
 			return -EINVAL;
 		push = args->v0.pushbuf;
 		head = args->v0.head;
diff --git a/drivers/gpu/drm/nouveau/nvkm/engine/disp/channv50.c b/drivers/gpu/drm/nouveau/nvkm/engine/disp/channv50.c
index 524a24eae1a0..0c0310498afd 100644
--- a/drivers/gpu/drm/nouveau/nvkm/engine/disp/channv50.c
+++ b/drivers/gpu/drm/nouveau/nvkm/engine/disp/channv50.c
@@ -25,6 +25,7 @@
 #include "rootnv50.h"
 
 #include <core/client.h>
+#include <core/notify.h>
 #include <core/ramht.h>
 #include <engine/dma.h>
 
diff --git a/drivers/gpu/drm/nouveau/nvkm/engine/disp/conn.c b/drivers/gpu/drm/nouveau/nvkm/engine/disp/conn.c
index c6910d644a3d..febc5c274488 100644
--- a/drivers/gpu/drm/nouveau/nvkm/engine/disp/conn.c
+++ b/drivers/gpu/drm/nouveau/nvkm/engine/disp/conn.c
@@ -30,9 +30,9 @@
 #include <nvif/event.h>
 
 static int
-nvkm_connector_hpd(struct nvkm_notify *notify)
+nvkm_conn_hpd(struct nvkm_notify *notify)
 {
-	struct nvkm_connector *conn = container_of(notify, typeof(*conn), hpd);
+	struct nvkm_conn *conn = container_of(notify, typeof(*conn), hpd);
 	struct nvkm_disp *disp = conn->disp;
 	struct nvkm_gpio *gpio = disp->engine.subdev.device->gpio;
 	const struct nvkm_gpio_ntfy_rep *line = notify->data;
@@ -52,21 +52,21 @@ nvkm_connector_hpd(struct nvkm_notify *notify)
 }
 
 void
-nvkm_connector_fini(struct nvkm_connector *conn)
+nvkm_conn_fini(struct nvkm_conn *conn)
 {
 	nvkm_notify_put(&conn->hpd);
 }
 
 void
-nvkm_connector_init(struct nvkm_connector *conn)
+nvkm_conn_init(struct nvkm_conn *conn)
 {
 	nvkm_notify_get(&conn->hpd);
 }
 
 void
-nvkm_connector_del(struct nvkm_connector **pconn)
+nvkm_conn_del(struct nvkm_conn **pconn)
 {
-	struct nvkm_connector *conn = *pconn;
+	struct nvkm_conn *conn = *pconn;
 	if (conn) {
 		nvkm_notify_fini(&conn->hpd);
 		kfree(*pconn);
@@ -75,8 +75,8 @@ nvkm_connector_del(struct nvkm_connector **pconn)
 }
 
 static void
-nvkm_connector_ctor(struct nvkm_disp *disp, int index,
-		    struct nvbios_connE *info, struct nvkm_connector *conn)
+nvkm_conn_ctor(struct nvkm_disp *disp, int index, struct nvbios_connE *info,
+	       struct nvkm_conn *conn)
 {
 	static const u8 hpd[] = { 0x07, 0x08, 0x51, 0x52, 0x5e, 0x5f, 0x60 };
 	struct nvkm_gpio *gpio = disp->engine.subdev.device->gpio;
@@ -105,7 +105,7 @@ nvkm_connector_ctor(struct nvkm_disp *disp, int index,
 			return;
 		}
 
-		ret = nvkm_notify_init(NULL, &gpio->event, nvkm_connector_hpd,
+		ret = nvkm_notify_init(NULL, &gpio->event, nvkm_conn_hpd,
 				       true, &(struct nvkm_gpio_ntfy_req) {
 					.mask = NVKM_GPIO_TOGGLED,
 					.line = func.line,
@@ -122,11 +122,11 @@ nvkm_connector_ctor(struct nvkm_disp *disp, int index,
 }
 
 int
-nvkm_connector_new(struct nvkm_disp *disp, int index,
-		   struct nvbios_connE *info, struct nvkm_connector **pconn)
+nvkm_conn_new(struct nvkm_disp *disp, int index, struct nvbios_connE *info,
+	      struct nvkm_conn **pconn)
 {
 	if (!(*pconn = kzalloc(sizeof(**pconn), GFP_KERNEL)))
 		return -ENOMEM;
-	nvkm_connector_ctor(disp, index, info, *pconn);
+	nvkm_conn_ctor(disp, index, info, *pconn);
 	return 0;
 }
diff --git a/drivers/gpu/drm/nouveau/nvkm/engine/disp/conn.h b/drivers/gpu/drm/nouveau/nvkm/engine/disp/conn.h
index ed32fe7f1864..de962b7b026d 100644
--- a/drivers/gpu/drm/nouveau/nvkm/engine/disp/conn.h
+++ b/drivers/gpu/drm/nouveau/nvkm/engine/disp/conn.h
@@ -6,7 +6,7 @@
 #include <subdev/bios.h>
 #include <subdev/bios/conn.h>
 
-struct nvkm_connector {
+struct nvkm_conn {
 	struct nvkm_disp *disp;
 	int index;
 	struct nvbios_connE info;
@@ -16,14 +16,14 @@ struct nvkm_connector {
 	struct list_head head;
 };
 
-int  nvkm_connector_new(struct nvkm_disp *, int index, struct nvbios_connE *,
-			struct nvkm_connector **);
-void nvkm_connector_del(struct nvkm_connector **);
-void nvkm_connector_init(struct nvkm_connector *);
-void nvkm_connector_fini(struct nvkm_connector *);
+int nvkm_conn_new(struct nvkm_disp *, int index, struct nvbios_connE *,
+		  struct nvkm_conn **);
+void nvkm_conn_del(struct nvkm_conn **);
+void nvkm_conn_init(struct nvkm_conn *);
+void nvkm_conn_fini(struct nvkm_conn *);
 
 #define CONN_MSG(c,l,f,a...) do {                                              \
-	struct nvkm_connector *_conn = (c);                                    \
+	struct nvkm_conn *_conn = (c);                                    \
 	nvkm_##l(&_conn->disp->engine.subdev, "conn %02x:%02x%02x: "f"\n",     \
 		 _conn->index, _conn->info.location, _conn->info.type, ##a);   \
 } while(0)
diff --git a/drivers/gpu/drm/nouveau/nvkm/engine/disp/cursnv50.c b/drivers/gpu/drm/nouveau/nvkm/engine/disp/cursnv50.c
index 82ff82d8c1ab..ab51121b7982 100644
--- a/drivers/gpu/drm/nouveau/nvkm/engine/disp/cursnv50.c
+++ b/drivers/gpu/drm/nouveau/nvkm/engine/disp/cursnv50.c
@@ -22,6 +22,7 @@
  * Authors: Ben Skeggs
  */
 #include "channv50.h"
+#include "head.h"
 #include "rootnv50.h"
 
 #include <core/client.h>
@@ -48,7 +49,7 @@ nv50_disp_curs_new(const struct nv50_disp_chan_func *func,
 	if (!(ret = nvif_unpack(ret, &data, &size, args->v0, 0, 0, false))) {
 		nvif_ioctl(parent, "create disp cursor vers %d head %d\n",
 			   args->v0.version, args->v0.head);
-		if (args->v0.head > disp->base.head.nr)
+		if (!nvkm_head_find(&disp->base, args->v0.head))
 			return -EINVAL;
 		head = args->v0.head;
 	} else
diff --git a/drivers/gpu/drm/nouveau/nvkm/engine/disp/dacgf119.c b/drivers/gpu/drm/nouveau/nvkm/engine/disp/dacgf119.c
new file mode 100644
index 000000000000..dbd032ef352a
--- /dev/null
+++ b/drivers/gpu/drm/nouveau/nvkm/engine/disp/dacgf119.c
@@ -0,0 +1,65 @@
+/*
+ * Copyright 2017 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.
+ */
+#include "ior.h"
+
+static void
+gf119_dac_clock(struct nvkm_ior *dac)
+{
+	struct nvkm_device *device = dac->disp->engine.subdev.device;
+	const u32 doff = nv50_ior_base(dac);
+	nvkm_mask(device, 0x612280 + doff, 0x07070707, 0x00000000);
+}
+
+static void
+gf119_dac_state(struct nvkm_ior *dac, struct nvkm_ior_state *state)
+{
+	struct nvkm_device *device = dac->disp->engine.subdev.device;
+	const u32 coff = (state == &dac->asy) * 0x20000 + dac->id * 0x20;
+	u32 ctrl = nvkm_rd32(device, 0x640180 + coff);
+
+	state->proto_evo = (ctrl & 0x00000f00) >> 8;
+	switch (state->proto_evo) {
+	case 0: state->proto = CRT; break;
+	default:
+		state->proto = UNKNOWN;
+		break;
+	}
+
+	state->head = ctrl & 0x0000000f;
+}
+
+static const struct nvkm_ior_func
+gf119_dac = {
+	.state = gf119_dac_state,
+	.power = nv50_dac_power,
+	.sense = nv50_dac_sense,
+	.clock = gf119_dac_clock,
+};
+
+int
+gf119_dac_new(struct nvkm_disp *disp, int id)
+{
+	struct nvkm_device *device = disp->engine.subdev.device;
+	if (!(nvkm_rd32(device, 0x612004) & (0x00000010 << id)))
+		return 0;
+	return nvkm_ior_new_(&gf119_dac, disp, DAC, id);
+}
diff --git a/drivers/gpu/drm/nouveau/nvkm/engine/disp/dacnv50.c b/drivers/gpu/drm/nouveau/nvkm/engine/disp/dacnv50.c
index c9b78b8f9c87..85e692b12260 100644
--- a/drivers/gpu/drm/nouveau/nvkm/engine/disp/dacnv50.c
+++ b/drivers/gpu/drm/nouveau/nvkm/engine/disp/dacnv50.c
@@ -21,106 +21,96 @@
  *
  * Authors: Ben Skeggs
  */
-#include "nv50.h"
-#include "outp.h"
+#include "ior.h"
 
-#include <core/client.h>
 #include <subdev/timer.h>
 
-#include <nvif/cl5070.h>
-#include <nvif/unpack.h>
-
-int
-nv50_dac_power(NV50_DISP_MTHD_V1)
+static void
+nv50_dac_clock(struct nvkm_ior *dac)
 {
-	struct nvkm_device *device = disp->base.engine.subdev.device;
-	const u32 doff = outp->or * 0x800;
-	union {
-		struct nv50_disp_dac_pwr_v0 v0;
-	} *args = data;
-	u32 stat;
-	int ret = -ENOSYS;
-
-	nvif_ioctl(object, "disp dac pwr size %d\n", size);
-	if (!(ret = nvif_unpack(ret, &data, &size, args->v0, 0, 0, false))) {
-		nvif_ioctl(object, "disp dac pwr vers %d state %d data %d "
-				   "vsync %d hsync %d\n",
-			   args->v0.version, args->v0.state, args->v0.data,
-			   args->v0.vsync, args->v0.hsync);
-		stat  = 0x00000040 * !args->v0.state;
-		stat |= 0x00000010 * !args->v0.data;
-		stat |= 0x00000004 * !args->v0.vsync;
-		stat |= 0x00000001 * !args->v0.hsync;
-	} else
-		return ret;
-
-	nvkm_msec(device, 2000,
-		if (!(nvkm_rd32(device, 0x61a004 + doff) & 0x80000000))
-			break;
-	);
-	nvkm_mask(device, 0x61a004 + doff, 0xc000007f, 0x80000000 | stat);
-	nvkm_msec(device, 2000,
-		if (!(nvkm_rd32(device, 0x61a004 + doff) & 0x80000000))
-			break;
-	);
-	return 0;
+	struct nvkm_device *device = dac->disp->engine.subdev.device;
+	const u32 doff = nv50_ior_base(dac);
+	nvkm_mask(device, 0x614280 + doff, 0x07070707, 0x00000000);
 }
 
 int
-nv50_dac_sense(NV50_DISP_MTHD_V1)
+nv50_dac_sense(struct nvkm_ior *dac, u32 loadval)
 {
-	struct nvkm_subdev *subdev = &disp->base.engine.subdev;
-	struct nvkm_device *device = subdev->device;
-	union {
-		struct nv50_disp_dac_load_v0 v0;
-	} *args = data;
-	const u32 doff = outp->or * 0x800;
-	u32 loadval;
-	int ret = -ENOSYS;
+	struct nvkm_device *device = dac->disp->engine.subdev.device;
+	const u32 doff = nv50_ior_base(dac);
 
-	nvif_ioctl(object, "disp dac load size %d\n", size);
-	if (!(ret = nvif_unpack(ret, &data, &size, args->v0, 0, 0, false))) {
-		nvif_ioctl(object, "disp dac load vers %d data %08x\n",
-			   args->v0.version, args->v0.data);
-		if (args->v0.data & 0xfff00000)
-			return -EINVAL;
-		loadval = args->v0.data;
-	} else
-		return ret;
-
-	nvkm_mask(device, 0x61a004 + doff, 0x807f0000, 0x80150000);
-	nvkm_msec(device, 2000,
-		if (!(nvkm_rd32(device, 0x61a004 + doff) & 0x80000000))
-			break;
-	);
+	dac->func->power(dac, false, true, false, false, false);
 
 	nvkm_wr32(device, 0x61a00c + doff, 0x00100000 | loadval);
 	mdelay(9);
 	udelay(500);
 	loadval = nvkm_mask(device, 0x61a00c + doff, 0xffffffff, 0x00000000);
 
-	nvkm_mask(device, 0x61a004 + doff, 0x807f0000, 0x80550000);
+	dac->func->power(dac, false, false, false, false, false);
+	if (!(loadval & 0x80000000))
+		return -ETIMEDOUT;
+
+	return (loadval & 0x38000000) >> 27;
+}
+
+static void
+nv50_dac_power_wait(struct nvkm_device *device, const u32 doff)
+{
 	nvkm_msec(device, 2000,
 		if (!(nvkm_rd32(device, 0x61a004 + doff) & 0x80000000))
 			break;
 	);
+}
 
-	nvkm_debug(subdev, "DAC%d sense: %08x\n", outp->or, loadval);
-	if (!(loadval & 0x80000000))
-		return -ETIMEDOUT;
+void
+nv50_dac_power(struct nvkm_ior *dac, bool normal, bool pu,
+	       bool data, bool vsync, bool hsync)
+{
+	struct nvkm_device *device = dac->disp->engine.subdev.device;
+	const u32  doff = nv50_ior_base(dac);
+	const u32 shift = normal ? 0 : 16;
+	const u32 state = 0x80000000 | (0x00000040 * !    pu |
+					0x00000010 * !  data |
+					0x00000004 * ! vsync |
+					0x00000001 * ! hsync) << shift;
+	const u32 field = 0xc0000000 | (0x00000055 << shift);
+
+	nv50_dac_power_wait(device, doff);
+	nvkm_mask(device, 0x61a004 + doff, field, state);
+	nv50_dac_power_wait(device, doff);
+}
+
+static void
+nv50_dac_state(struct nvkm_ior *dac, struct nvkm_ior_state *state)
+{
+	struct nvkm_device *device = dac->disp->engine.subdev.device;
+	const u32 coff = dac->id * 8 + (state == &dac->arm) * 4;
+	u32 ctrl = nvkm_rd32(device, 0x610b58 + coff);
+
+	state->proto_evo = (ctrl & 0x00000f00) >> 8;
+	switch (state->proto_evo) {
+	case 0: state->proto = CRT; break;
+	default:
+		state->proto = UNKNOWN;
+		break;
+	}
 
-	args->v0.load = (loadval & 0x38000000) >> 27;
-	return 0;
+	state->head = ctrl & 0x00000003;
 }
 
-static const struct nvkm_output_func
-nv50_dac_output_func = {
+static const struct nvkm_ior_func
+nv50_dac = {
+	.state = nv50_dac_state,
+	.power = nv50_dac_power,
+	.sense = nv50_dac_sense,
+	.clock = nv50_dac_clock,
 };
 
 int
-nv50_dac_output_new(struct nvkm_disp *disp, int index,
-		    struct dcb_output *dcbE, struct nvkm_output **poutp)
+nv50_dac_new(struct nvkm_disp *disp, int id)
 {
-	return nvkm_output_new_(&nv50_dac_output_func, disp,
-				index, dcbE, poutp);
+	struct nvkm_device *device = disp->engine.subdev.device;
+	if (!(nvkm_rd32(device, 0x610184) & (0x00100000 << id)))
+		return 0;
+	return nvkm_ior_new_(&nv50_dac, disp, DAC, id);
 }
diff --git a/drivers/gpu/drm/nouveau/nvkm/engine/disp/dp.c b/drivers/gpu/drm/nouveau/nvkm/engine/disp/dp.c
new file mode 100644
index 000000000000..7c5bed29ffef
--- /dev/null
+++ b/drivers/gpu/drm/nouveau/nvkm/engine/disp/dp.c
@@ -0,0 +1,652 @@
+/*
+ * 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 "head.h"
+#include "ior.h"
+
+#include <subdev/bios.h>
+#include <subdev/bios/init.h>
+#include <subdev/i2c.h>
+
+#include <nvif/event.h>
+
+struct lt_state {
+	struct nvkm_dp *dp;
+	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)
+{
+	struct nvkm_dp *dp = lt->dp;
+	int ret;
+
+	if (dp->dpcd[DPCD_RC0E_AUX_RD_INTERVAL])
+		mdelay(dp->dpcd[DPCD_RC0E_AUX_RD_INTERVAL] * 4);
+	else
+		udelay(delay);
+
+	ret = nvkm_rdaux(dp->aux, DPCD_LS02, lt->stat, 6);
+	if (ret)
+		return ret;
+
+	if (pc) {
+		ret = nvkm_rdaux(dp->aux, DPCD_LS0C, &lt->pc2stat, 1);
+		if (ret)
+			lt->pc2stat = 0x00;
+		OUTP_TRACE(&dp->outp, "status %6ph pc2 %02x",
+			   lt->stat, lt->pc2stat);
+	} else {
+		OUTP_TRACE(&dp->outp, "status %6ph", lt->stat);
+	}
+
+	return 0;
+}
+
+static int
+nvkm_dp_train_drive(struct lt_state *lt, bool pc)
+{
+	struct nvkm_dp *dp = lt->dp;
+	struct nvkm_ior *ior = dp->outp.ior;
+	struct nvkm_bios *bios = ior->disp->engine.subdev.device->bios;
+	struct nvbios_dpout info;
+	struct nvbios_dpcfg ocfg;
+	u8  ver, hdr, cnt, len;
+	u32 data;
+	int ret, i;
+
+	for (i = 0; i < ior->dp.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);
+
+		OUTP_TRACE(&dp->outp, "config lane %d %02x %02x",
+			   i, lt->conf[i], lpc2);
+
+		data = nvbios_dpout_match(bios, dp->outp.info.hasht,
+						dp->outp.info.hashm,
+					  &ver, &hdr, &cnt, &len, &info);
+		if (!data)
+			continue;
+
+		data = nvbios_dpcfg_match(bios, data, lpc2 & 3, lvsw & 3,
+					  lpre & 3, &ver, &hdr, &cnt, &len,
+					  &ocfg);
+		if (!data)
+			continue;
+
+		ior->func->dp.drive(ior, i, ocfg.pc, ocfg.dc,
+					    ocfg.pe, ocfg.tx_pu);
+	}
+
+	ret = nvkm_wraux(dp->aux, DPCD_LC03(0), lt->conf, 4);
+	if (ret)
+		return ret;
+
+	if (pc) {
+		ret = nvkm_wraux(dp->aux, DPCD_LC0F, lt->pc2conf, 2);
+		if (ret)
+			return ret;
+	}
+
+	return 0;
+}
+
+static void
+nvkm_dp_train_pattern(struct lt_state *lt, u8 pattern)
+{
+	struct nvkm_dp *dp = lt->dp;
+	u8 sink_tp;
+
+	OUTP_TRACE(&dp->outp, "training pattern %d", pattern);
+	dp->outp.ior->func->dp.pattern(dp->outp.ior, pattern);
+
+	nvkm_rdaux(dp->aux, DPCD_LC02, &sink_tp, 1);
+	sink_tp &= ~DPCD_LC02_TRAINING_PATTERN_SET;
+	sink_tp |= pattern;
+	nvkm_wraux(dp->aux, DPCD_LC02, &sink_tp, 1);
+}
+
+static int
+nvkm_dp_train_eq(struct lt_state *lt)
+{
+	bool eq_done = false, cr_done = true;
+	int tries = 0, i;
+
+	if (lt->dp->dpcd[DPCD_RC02] & DPCD_RC02_TPS3_SUPPORTED)
+		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->dp->outp.ior->dp.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->dp->outp.ior->dp.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 nvkm_dp *dp)
+{
+	struct nvkm_ior *ior = dp->outp.ior;
+	struct nvkm_disp *disp = dp->outp.disp;
+	struct nvkm_subdev *subdev = &disp->engine.subdev;
+	struct nvkm_bios *bios = subdev->device->bios;
+	struct lt_state lt = {
+		.dp = dp,
+	};
+	u32 lnkcmp;
+	u8 sink[2];
+	int ret;
+
+	OUTP_DBG(&dp->outp, "training %d x %d MB/s",
+		 ior->dp.nr, ior->dp.bw * 27);
+
+	/* Intersect misc. capabilities of the OR and sink. */
+	if (disp->engine.subdev.device->chipset < 0xd0)
+		dp->dpcd[DPCD_RC02] &= ~DPCD_RC02_TPS3_SUPPORTED;
+	lt.pc2 = dp->dpcd[DPCD_RC02] & DPCD_RC02_TPS3_SUPPORTED;
+
+	/* Set desired link configuration on the source. */
+	if ((lnkcmp = lt.dp->info.lnkcmp)) {
+		if (dp->version < 0x30) {
+			while ((ior->dp.bw * 2700) < nvbios_rd16(bios, lnkcmp))
+				lnkcmp += 4;
+			lnkcmp = nvbios_rd16(bios, lnkcmp + 2);
+		} else {
+			while (ior->dp.bw < nvbios_rd08(bios, lnkcmp))
+				lnkcmp += 3;
+			lnkcmp = nvbios_rd16(bios, lnkcmp + 1);
+		}
+
+		nvbios_init(subdev, lnkcmp,
+			init.outp = &dp->outp.info;
+			init.or   = ior->id;
+			init.link = ior->asy.link;
+		);
+	}
+
+	ret = ior->func->dp.links(ior, dp->aux);
+	if (ret) {
+		if (ret < 0) {
+			OUTP_ERR(&dp->outp, "train failed with %d", ret);
+			return ret;
+		}
+		return 0;
+	}
+
+	ior->func->dp.power(ior, ior->dp.nr);
+
+	/* Set desired link configuration on the sink. */
+	sink[0] = ior->dp.bw;
+	sink[1] = ior->dp.nr;
+	if (ior->dp.ef)
+		sink[1] |= DPCD_LC01_ENHANCED_FRAME_EN;
+
+	ret = nvkm_wraux(dp->aux, DPCD_LC00_LINK_BW_SET, sink, 2);
+	if (ret)
+		return ret;
+
+	/* Attempt to train the link in this configuration. */
+	memset(lt.stat, 0x00, sizeof(lt.stat));
+	ret = nvkm_dp_train_cr(&lt);
+	if (ret == 0)
+		ret = nvkm_dp_train_eq(&lt);
+	nvkm_dp_train_pattern(&lt, 0);
+	return ret;
+}
+
+static void
+nvkm_dp_train_fini(struct nvkm_dp *dp)
+{
+	/* Execute AfterLinkTraining script from DP Info table. */
+	nvbios_init(&dp->outp.disp->engine.subdev, dp->info.script[1],
+		init.outp = &dp->outp.info;
+		init.or   = dp->outp.ior->id;
+		init.link = dp->outp.ior->asy.link;
+	);
+}
+
+static void
+nvkm_dp_train_init(struct nvkm_dp *dp)
+{
+	/* Execute EnableSpread/DisableSpread script from DP Info table. */
+	if (dp->dpcd[DPCD_RC03] & DPCD_RC03_MAX_DOWNSPREAD) {
+		nvbios_init(&dp->outp.disp->engine.subdev, dp->info.script[2],
+			init.outp = &dp->outp.info;
+			init.or   = dp->outp.ior->id;
+			init.link = dp->outp.ior->asy.link;
+		);
+	} else {
+		nvbios_init(&dp->outp.disp->engine.subdev, dp->info.script[3],
+			init.outp = &dp->outp.info;
+			init.or   = dp->outp.ior->id;
+			init.link = dp->outp.ior->asy.link;
+		);
+	}
+
+	/* Execute BeforeLinkTraining script from DP Info table. */
+	nvbios_init(&dp->outp.disp->engine.subdev, dp->info.script[0],
+		init.outp = &dp->outp.info;
+		init.or   = dp->outp.ior->id;
+		init.link = dp->outp.ior->asy.link;
+	);
+}
+
+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 int
+nvkm_dp_train(struct nvkm_dp *dp, u32 dataKBps)
+{
+	struct nvkm_ior *ior = dp->outp.ior;
+	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 outp_nr = dp->outp.info.dpconf.link_nr;
+	const u8 outp_bw = dp->outp.info.dpconf.link_bw;
+	const struct dp_rates *failsafe = NULL, *cfg;
+	int ret = -EINVAL;
+	u8  pwr;
+
+	/* Find the lowest configuration of the OR that can support
+	 * the required link rate.
+	 *
+	 * We will refuse to program the OR to lower rates, even if
+	 * link training fails at higher rates (or even if the sink
+	 * can't support the rate at all, though the DD is supposed
+	 * to prevent such situations from happening).
+	 *
+	 * Attempting to do so can cause the entire display to hang,
+	 * and it's better to have a failed modeset than that.
+	 */
+	for (cfg = nvkm_dp_rates; cfg->rate; cfg++) {
+		if (cfg->nr <= outp_nr && cfg->nr <= outp_bw)
+			failsafe = cfg;
+		if (failsafe && cfg[1].rate < dataKBps)
+			break;
+	}
+
+	if (WARN_ON(!failsafe))
+		return ret;
+
+	/* Ensure sink is not in a low-power state. */
+	if (!nvkm_rdaux(dp->aux, DPCD_SC00, &pwr, 1)) {
+		if ((pwr & DPCD_SC00_SET_POWER) != DPCD_SC00_SET_POWER_D0) {
+			pwr &= ~DPCD_SC00_SET_POWER;
+			pwr |=  DPCD_SC00_SET_POWER_D0;
+			nvkm_wraux(dp->aux, DPCD_SC00, &pwr, 1);
+		}
+	}
+
+	/* Link training. */
+	OUTP_DBG(&dp->outp, "training (min: %d x %d MB/s)",
+		 failsafe->nr, failsafe->bw * 27);
+	nvkm_dp_train_init(dp);
+	for (cfg = nvkm_dp_rates; ret < 0 && cfg <= failsafe; cfg++) {
+		/* Skip configurations not supported by both OR and sink. */
+		if ((cfg->nr > outp_nr || cfg->bw > outp_bw ||
+		     cfg->nr > sink_nr || cfg->bw > sink_bw)) {
+			if (cfg != failsafe)
+				continue;
+			OUTP_ERR(&dp->outp, "link rate unsupported by sink");
+		}
+		ior->dp.mst = dp->lt.mst;
+		ior->dp.ef = dp->dpcd[DPCD_RC02] & DPCD_RC02_ENHANCED_FRAME_CAP;
+		ior->dp.bw = cfg->bw;
+		ior->dp.nr = cfg->nr;
+
+		/* Program selected link configuration. */
+		ret = nvkm_dp_train_links(dp);
+	}
+	nvkm_dp_train_fini(dp);
+	if (ret < 0)
+		OUTP_ERR(&dp->outp, "training failed");
+	else
+		OUTP_DBG(&dp->outp, "training done");
+	atomic_set(&dp->lt.done, 1);
+	return ret;
+}
+
+static void
+nvkm_dp_release(struct nvkm_outp *outp, struct nvkm_ior *ior)
+{
+	struct nvkm_dp *dp = nvkm_dp(outp);
+
+	/* Prevent link from being retrained if sink sends an IRQ. */
+	atomic_set(&dp->lt.done, 0);
+	ior->dp.nr = 0;
+
+	/* Execute DisableLT script from DP Info Table. */
+	nvbios_init(&ior->disp->engine.subdev, dp->info.script[4],
+		init.outp = &dp->outp.info;
+		init.or   = ior->id;
+		init.link = ior->arm.link;
+	);
+}
+
+static int
+nvkm_dp_acquire(struct nvkm_outp *outp)
+{
+	struct nvkm_dp *dp = nvkm_dp(outp);
+	struct nvkm_ior *ior = dp->outp.ior;
+	struct nvkm_head *head;
+	bool retrain = true;
+	u32 datakbps = 0;
+	u32 dataKBps;
+	u32 linkKBps;
+	u8  stat[3];
+	int ret, i;
+
+	mutex_lock(&dp->mutex);
+
+	/* Check that link configuration meets current requirements. */
+	list_for_each_entry(head, &outp->disp->head, head) {
+		if (ior->asy.head & (1 << head->id)) {
+			u32 khz = (head->asy.hz >> ior->asy.rgdiv) / 1000;
+			datakbps += khz * head->asy.or.depth;
+		}
+	}
+
+	linkKBps = ior->dp.bw * 27000 * ior->dp.nr;
+	dataKBps = DIV_ROUND_UP(datakbps, 8);
+	OUTP_DBG(&dp->outp, "data %d KB/s link %d KB/s mst %d->%d",
+		 dataKBps, linkKBps, ior->dp.mst, dp->lt.mst);
+	if (linkKBps < dataKBps || ior->dp.mst != dp->lt.mst) {
+		OUTP_DBG(&dp->outp, "link requirements changed");
+		goto done;
+	}
+
+	/* Check that link is still trained. */
+	ret = nvkm_rdaux(dp->aux, DPCD_LS02, stat, 3);
+	if (ret) {
+		OUTP_DBG(&dp->outp,
+			 "failed to read link status, assuming no sink");
+		goto done;
+	}
+
+	if (stat[2] & DPCD_LS04_INTERLANE_ALIGN_DONE) {
+		for (i = 0; i < ior->dp.nr; 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)) {
+				OUTP_DBG(&dp->outp,
+					 "lane %d not equalised", lane);
+				goto done;
+			}
+		}
+		retrain = false;
+	} else {
+		OUTP_DBG(&dp->outp, "no inter-lane alignment");
+	}
+
+done:
+	if (retrain || !atomic_read(&dp->lt.done))
+		ret = nvkm_dp_train(dp, dataKBps);
+	mutex_unlock(&dp->mutex);
+	return ret;
+}
+
+static void
+nvkm_dp_enable(struct nvkm_dp *dp, bool enable)
+{
+	struct nvkm_i2c_aux *aux = dp->aux;
+
+	if (enable) {
+		if (!dp->present) {
+			OUTP_DBG(&dp->outp, "aux power -> always");
+			nvkm_i2c_aux_monitor(aux, true);
+			dp->present = true;
+		}
+
+		if (!nvkm_rdaux(aux, DPCD_RC00_DPCD_REV, dp->dpcd,
+				sizeof(dp->dpcd)))
+			return;
+	}
+
+	if (dp->present) {
+		OUTP_DBG(&dp->outp, "aux power -> demand");
+		nvkm_i2c_aux_monitor(aux, false);
+		dp->present = false;
+	}
+
+	atomic_set(&dp->lt.done, 0);
+}
+
+static int
+nvkm_dp_hpd(struct nvkm_notify *notify)
+{
+	const struct nvkm_i2c_ntfy_rep *line = notify->data;
+	struct nvkm_dp *dp = container_of(notify, typeof(*dp), hpd);
+	struct nvkm_conn *conn = dp->outp.conn;
+	struct nvkm_disp *disp = dp->outp.disp;
+	struct nvif_notify_conn_rep_v0 rep = {};
+
+	OUTP_DBG(&dp->outp, "HPD: %d", line->mask);
+	if (line->mask & NVKM_I2C_IRQ) {
+		if (atomic_read(&dp->lt.done))
+			dp->outp.func->acquire(&dp->outp);
+		rep.mask |= NVIF_NOTIFY_CONN_V0_IRQ;
+	} else {
+		nvkm_dp_enable(dp, true);
+	}
+
+	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 void
+nvkm_dp_fini(struct nvkm_outp *outp)
+{
+	struct nvkm_dp *dp = nvkm_dp(outp);
+	nvkm_notify_put(&dp->hpd);
+	nvkm_dp_enable(dp, false);
+}
+
+static void
+nvkm_dp_init(struct nvkm_outp *outp)
+{
+	struct nvkm_dp *dp = nvkm_dp(outp);
+	nvkm_notify_put(&dp->outp.conn->hpd);
+	nvkm_dp_enable(dp, true);
+	nvkm_notify_get(&dp->hpd);
+}
+
+static void *
+nvkm_dp_dtor(struct nvkm_outp *outp)
+{
+	struct nvkm_dp *dp = nvkm_dp(outp);
+	nvkm_notify_fini(&dp->hpd);
+	return dp;
+}
+
+static const struct nvkm_outp_func
+nvkm_dp_func = {
+	.dtor = nvkm_dp_dtor,
+	.init = nvkm_dp_init,
+	.fini = nvkm_dp_fini,
+	.acquire = nvkm_dp_acquire,
+	.release = nvkm_dp_release,
+};
+
+static int
+nvkm_dp_ctor(struct nvkm_disp *disp, int index, struct dcb_output *dcbE,
+	     struct nvkm_i2c_aux *aux, struct nvkm_dp *dp)
+{
+	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;
+
+	ret = nvkm_outp_ctor(&nvkm_dp_func, disp, index, dcbE, &dp->outp);
+	if (ret)
+		return ret;
+
+	dp->aux = aux;
+	if (!dp->aux) {
+		OUTP_ERR(&dp->outp, "no aux");
+		return -EINVAL;
+	}
+
+	/* bios data is not optional */
+	data = nvbios_dpout_match(bios, dp->outp.info.hasht,
+				  dp->outp.info.hashm, &dp->version,
+				  &hdr, &cnt, &len, &dp->info);
+	if (!data) {
+		OUTP_ERR(&dp->outp, "no bios dp data");
+		return -EINVAL;
+	}
+
+	OUTP_DBG(&dp->outp, "bios dp %02x %02x %02x %02x",
+		 dp->version, hdr, cnt, len);
+
+	/* hotplug detect, replaces gpio-based mechanism with aux events */
+	ret = nvkm_notify_init(NULL, &i2c->event, nvkm_dp_hpd, true,
+			       &(struct nvkm_i2c_ntfy_req) {
+				.mask = NVKM_I2C_PLUG | NVKM_I2C_UNPLUG |
+					NVKM_I2C_IRQ,
+				.port = dp->aux->id,
+			       },
+			       sizeof(struct nvkm_i2c_ntfy_req),
+			       sizeof(struct nvkm_i2c_ntfy_rep),
+			       &dp->hpd);
+	if (ret) {
+		OUTP_ERR(&dp->outp, "error monitoring aux hpd: %d", ret);
+		return ret;
+	}
+
+	mutex_init(&dp->mutex);
+	atomic_set(&dp->lt.done, 0);
+	return 0;
+}
+
+int
+nvkm_dp_new(struct nvkm_disp *disp, int index, struct dcb_output *dcbE,
+	    struct nvkm_outp **poutp)
+{
+	struct nvkm_i2c *i2c = disp->engine.subdev.device->i2c;
+	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));
+
+	if (!(dp = kzalloc(sizeof(*dp), GFP_KERNEL)))
+		return -ENOMEM;
+	*poutp = &dp->outp;
+
+	return nvkm_dp_ctor(disp, index, dcbE, aux, dp);
+}
diff --git a/drivers/gpu/drm/nouveau/nvkm/engine/disp/dport.h b/drivers/gpu/drm/nouveau/nvkm/engine/disp/dp.h
index baf1dd9ff975..59173c290525 100644
--- a/drivers/gpu/drm/nouveau/nvkm/engine/disp/dport.h
+++ b/drivers/gpu/drm/nouveau/nvkm/engine/disp/dp.h
@@ -1,6 +1,36 @@
-#ifndef __NVKM_DISP_DPORT_H__
-#define __NVKM_DISP_DPORT_H__
-struct nvkm_output_dp;
+#ifndef __NVKM_DISP_DP_H__
+#define __NVKM_DISP_DP_H__
+#define nvkm_dp(p) container_of((p), struct nvkm_dp, outp)
+#include "outp.h"
+
+#include <core/notify.h>
+#include <subdev/bios.h>
+#include <subdev/bios/dp.h>
+
+struct nvkm_dp {
+	union {
+		struct nvkm_outp base;
+		struct nvkm_outp outp;
+	};
+
+	struct nvbios_dpout info;
+	u8 version;
+
+	struct nvkm_i2c_aux *aux;
+
+	struct nvkm_notify hpd;
+	bool present;
+	u8 dpcd[16];
+
+	struct mutex mutex;
+	struct {
+		atomic_t done;
+		bool mst;
+	} lt;
+};
+
+int nvkm_dp_new(struct nvkm_disp *, int index, struct dcb_output *,
+		struct nvkm_outp **);
 
 /* DPCD Receiver Capabilities */
 #define DPCD_RC00_DPCD_REV                                              0x00000
@@ -76,6 +106,4 @@ struct nvkm_output_dp;
 #define DPCD_SC00_SET_POWER                                                0x03
 #define DPCD_SC00_SET_POWER_D0                                             0x01
 #define DPCD_SC00_SET_POWER_D3                                             0x03
-
-void nvkm_dp_train(struct nvkm_output_dp *);
 #endif
diff --git a/drivers/gpu/drm/nouveau/nvkm/engine/disp/dport.c b/drivers/gpu/drm/nouveau/nvkm/engine/disp/dport.c
deleted file mode 100644
index 4a93ceb850ac..000000000000
--- a/drivers/gpu/drm/nouveau/nvkm/engine/disp/dport.c
+++ /dev/null
@@ -1,401 +0,0 @@
-/*
- * Copyright 2013 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 "dport.h"
-#include "outpdp.h"
-#include "nv50.h"
-
-#include <subdev/bios.h>
-#include <subdev/bios/init.h>
-#include <subdev/i2c.h>
-
-#include <nvif/class.h>
-
-/******************************************************************************
- * link training
- *****************************************************************************/
-struct dp_state {
-	struct nvkm_output_dp *outp;
-	int link_nr;
-	u32 link_bw;
-	u8  stat[6];
-	u8  conf[4];
-	bool pc2;
-	u8  pc2stat;
-	u8  pc2conf[2];
-};
-
-static int
-dp_set_link_config(struct dp_state *dp)
-{
-	struct nvkm_output_dp *outp = dp->outp;
-	struct nvkm_disp *disp = outp->base.disp;
-	struct nvkm_subdev *subdev = &disp->engine.subdev;
-	struct nvkm_bios *bios = subdev->device->bios;
-	struct nvbios_init init = {
-		.subdev = subdev,
-		.bios = bios,
-		.offset = 0x0000,
-		.outp = &outp->base.info,
-		.crtc = -1,
-		.execute = 1,
-	};
-	u32 lnkcmp;
-	u8 sink[2];
-	int ret;
-
-	OUTP_DBG(&outp->base, "%d lanes at %d KB/s", dp->link_nr, dp->link_bw);
-
-	/* set desired link configuration on the source */
-	if ((lnkcmp = dp->outp->info.lnkcmp)) {
-		if (outp->version < 0x30) {
-			while ((dp->link_bw / 10) < nvbios_rd16(bios, lnkcmp))
-				lnkcmp += 4;
-			init.offset = nvbios_rd16(bios, lnkcmp + 2);
-		} else {
-			while ((dp->link_bw / 27000) < nvbios_rd08(bios, lnkcmp))
-				lnkcmp += 3;
-			init.offset = nvbios_rd16(bios, lnkcmp + 1);
-		}
-
-		nvbios_exec(&init);
-	}
-
-	ret = outp->func->lnk_ctl(outp, dp->link_nr, dp->link_bw / 27000,
-				  outp->dpcd[DPCD_RC02] &
-					     DPCD_RC02_ENHANCED_FRAME_CAP);
-	if (ret) {
-		if (ret < 0)
-			OUTP_ERR(&outp->base, "lnk_ctl failed with %d", ret);
-		return ret;
-	}
-
-	outp->func->lnk_pwr(outp, dp->link_nr);
-
-	/* set desired link configuration on the sink */
-	sink[0] = dp->link_bw / 27000;
-	sink[1] = dp->link_nr;
-	if (outp->dpcd[DPCD_RC02] & DPCD_RC02_ENHANCED_FRAME_CAP)
-		sink[1] |= DPCD_LC01_ENHANCED_FRAME_EN;
-
-	return nvkm_wraux(outp->aux, DPCD_LC00_LINK_BW_SET, sink, 2);
-}
-
-static void
-dp_set_training_pattern(struct dp_state *dp, u8 pattern)
-{
-	struct nvkm_output_dp *outp = dp->outp;
-	u8 sink_tp;
-
-	OUTP_DBG(&outp->base, "training pattern %d", pattern);
-	outp->func->pattern(outp, pattern);
-
-	nvkm_rdaux(outp->aux, DPCD_LC02, &sink_tp, 1);
-	sink_tp &= ~DPCD_LC02_TRAINING_PATTERN_SET;
-	sink_tp |= pattern;
-	nvkm_wraux(outp->aux, DPCD_LC02, &sink_tp, 1);
-}
-
-static int
-dp_link_train_commit(struct dp_state *dp, bool pc)
-{
-	struct nvkm_output_dp *outp = dp->outp;
-	int ret, i;
-
-	for (i = 0; i < dp->link_nr; i++) {
-		u8 lane = (dp->stat[4 + (i >> 1)] >> ((i & 1) * 4)) & 0xf;
-		u8 lpc2 = (dp->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;
-		}
-
-		dp->conf[i] = (lpre << 3) | lvsw;
-		dp->pc2conf[i >> 1] |= lpc2 << ((i & 1) * 4);
-
-		OUTP_DBG(&outp->base, "config lane %d %02x %02x",
-			 i, dp->conf[i], lpc2);
-		outp->func->drv_ctl(outp, i, lvsw & 3, lpre & 3, lpc2 & 3);
-	}
-
-	ret = nvkm_wraux(outp->aux, DPCD_LC03(0), dp->conf, 4);
-	if (ret)
-		return ret;
-
-	if (pc) {
-		ret = nvkm_wraux(outp->aux, DPCD_LC0F, dp->pc2conf, 2);
-		if (ret)
-			return ret;
-	}
-
-	return 0;
-}
-
-static int
-dp_link_train_update(struct dp_state *dp, bool pc, u32 delay)
-{
-	struct nvkm_output_dp *outp = dp->outp;
-	int ret;
-
-	if (outp->dpcd[DPCD_RC0E_AUX_RD_INTERVAL])
-		mdelay(outp->dpcd[DPCD_RC0E_AUX_RD_INTERVAL] * 4);
-	else
-		udelay(delay);
-
-	ret = nvkm_rdaux(outp->aux, DPCD_LS02, dp->stat, 6);
-	if (ret)
-		return ret;
-
-	if (pc) {
-		ret = nvkm_rdaux(outp->aux, DPCD_LS0C, &dp->pc2stat, 1);
-		if (ret)
-			dp->pc2stat = 0x00;
-		OUTP_DBG(&outp->base, "status %6ph pc2 %02x",
-			 dp->stat, dp->pc2stat);
-	} else {
-		OUTP_DBG(&outp->base, "status %6ph", dp->stat);
-	}
-
-	return 0;
-}
-
-static int
-dp_link_train_cr(struct dp_state *dp)
-{
-	bool cr_done = false, abort = false;
-	int voltage = dp->conf[0] & DPCD_LC03_VOLTAGE_SWING_SET;
-	int tries = 0, i;
-
-	dp_set_training_pattern(dp, 1);
-
-	do {
-		if (dp_link_train_commit(dp, false) ||
-		    dp_link_train_update(dp, false, 100))
-			break;
-
-		cr_done = true;
-		for (i = 0; i < dp->link_nr; i++) {
-			u8 lane = (dp->stat[i >> 1] >> ((i & 1) * 4)) & 0xf;
-			if (!(lane & DPCD_LS02_LANE0_CR_DONE)) {
-				cr_done = false;
-				if (dp->conf[i] & DPCD_LC03_MAX_SWING_REACHED)
-					abort = true;
-				break;
-			}
-		}
-
-		if ((dp->conf[0] & DPCD_LC03_VOLTAGE_SWING_SET) != voltage) {
-			voltage = dp->conf[0] & DPCD_LC03_VOLTAGE_SWING_SET;
-			tries = 0;
-		}
-	} while (!cr_done && !abort && ++tries < 5);
-
-	return cr_done ? 0 : -1;
-}
-
-static int
-dp_link_train_eq(struct dp_state *dp)
-{
-	struct nvkm_output_dp *outp = dp->outp;
-	bool eq_done = false, cr_done = true;
-	int tries = 0, i;
-
-	if (outp->dpcd[2] & DPCD_RC02_TPS3_SUPPORTED)
-		dp_set_training_pattern(dp, 3);
-	else
-		dp_set_training_pattern(dp, 2);
-
-	do {
-		if ((tries &&
-		    dp_link_train_commit(dp, dp->pc2)) ||
-		    dp_link_train_update(dp, dp->pc2, 400))
-			break;
-
-		eq_done = !!(dp->stat[2] & DPCD_LS04_INTERLANE_ALIGN_DONE);
-		for (i = 0; i < dp->link_nr && eq_done; i++) {
-			u8 lane = (dp->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 void
-dp_link_train_init(struct dp_state *dp, bool spread)
-{
-	struct nvkm_output_dp *outp = dp->outp;
-	struct nvkm_disp *disp = outp->base.disp;
-	struct nvkm_subdev *subdev = &disp->engine.subdev;
-	struct nvbios_init init = {
-		.subdev = subdev,
-		.bios = subdev->device->bios,
-		.outp = &outp->base.info,
-		.crtc = -1,
-		.execute = 1,
-	};
-
-	/* set desired spread */
-	if (spread)
-		init.offset = outp->info.script[2];
-	else
-		init.offset = outp->info.script[3];
-	nvbios_exec(&init);
-
-	/* pre-train script */
-	init.offset = outp->info.script[0];
-	nvbios_exec(&init);
-}
-
-static void
-dp_link_train_fini(struct dp_state *dp)
-{
-	struct nvkm_output_dp *outp = dp->outp;
-	struct nvkm_disp *disp = outp->base.disp;
-	struct nvkm_subdev *subdev = &disp->engine.subdev;
-	struct nvbios_init init = {
-		.subdev = subdev,
-		.bios = subdev->device->bios,
-		.outp = &outp->base.info,
-		.crtc = -1,
-		.execute = 1,
-	};
-
-	/* post-train script */
-	init.offset = outp->info.script[1],
-	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 },
-	{}
-};
-
-void
-nvkm_dp_train(struct nvkm_output_dp *outp)
-{
-	struct nv50_disp *disp = nv50_disp(outp->base.disp);
-	const struct dp_rates *cfg = nvkm_dp_rates;
-	struct dp_state _dp = {
-		.outp = outp,
-	}, *dp = &_dp;
-	u32 datarate = 0;
-	u8  pwr;
-	int ret;
-
-	if (!outp->base.info.location && disp->func->sor.magic)
-		disp->func->sor.magic(&outp->base);
-
-	/* bring capabilities within encoder limits */
-	if (disp->base.engine.subdev.device->chipset < 0xd0)
-		outp->dpcd[2] &= ~DPCD_RC02_TPS3_SUPPORTED;
-	if ((outp->dpcd[2] & 0x1f) > outp->base.info.dpconf.link_nr) {
-		outp->dpcd[2] &= ~DPCD_RC02_MAX_LANE_COUNT;
-		outp->dpcd[2] |= outp->base.info.dpconf.link_nr;
-	}
-	if (outp->dpcd[1] > outp->base.info.dpconf.link_bw)
-		outp->dpcd[1] = outp->base.info.dpconf.link_bw;
-	dp->pc2 = outp->dpcd[2] & DPCD_RC02_TPS3_SUPPORTED;
-
-	/* restrict link config to the lowest required rate, if requested */
-	if (datarate) {
-		datarate = (datarate / 8) * 10; /* 8B/10B coding overhead */
-		while (cfg[1].rate >= datarate)
-			cfg++;
-	}
-	cfg--;
-
-	/* ensure sink is not in a low-power state */
-	if (!nvkm_rdaux(outp->aux, DPCD_SC00, &pwr, 1)) {
-		if ((pwr & DPCD_SC00_SET_POWER) != DPCD_SC00_SET_POWER_D0) {
-			pwr &= ~DPCD_SC00_SET_POWER;
-			pwr |=  DPCD_SC00_SET_POWER_D0;
-			nvkm_wraux(outp->aux, DPCD_SC00, &pwr, 1);
-		}
-	}
-
-	/* enable down-spreading and execute pre-train script from vbios */
-	dp_link_train_init(dp, outp->dpcd[3] & 0x01);
-
-	while (ret = -EIO, (++cfg)->rate) {
-		/* select next configuration supported by encoder and sink */
-		while (cfg->nr > (outp->dpcd[2] & DPCD_RC02_MAX_LANE_COUNT) ||
-		       cfg->bw > (outp->dpcd[DPCD_RC01_MAX_LINK_RATE]))
-			cfg++;
-		dp->link_bw = cfg->bw * 27000;
-		dp->link_nr = cfg->nr;
-
-		/* program selected link configuration */
-		ret = dp_set_link_config(dp);
-		if (ret == 0) {
-			/* attempt to train the link at this configuration */
-			memset(dp->stat, 0x00, sizeof(dp->stat));
-			if (!dp_link_train_cr(dp) &&
-			    !dp_link_train_eq(dp))
-				break;
-		} else
-		if (ret) {
-			/* dp_set_link_config() handled training, or
-			 * we failed to communicate with the sink.
-			 */
-			break;
-		}
-	}
-
-	/* finish link training and execute post-train script from vbios */
-	dp_set_training_pattern(dp, 0);
-	if (ret < 0)
-		OUTP_ERR(&outp->base, "link training failed");
-
-	dp_link_train_fini(dp);
-
-	OUTP_DBG(&outp->base, "training complete");
-	atomic_set(&outp->lt.done, 1);
-}
diff --git a/drivers/gpu/drm/nouveau/nvkm/engine/disp/g84.c b/drivers/gpu/drm/nouveau/nvkm/engine/disp/g84.c
index 3e3e592cd09f..842e1b72ee42 100644
--- a/drivers/gpu/drm/nouveau/nvkm/engine/disp/g84.c
+++ b/drivers/gpu/drm/nouveau/nvkm/engine/disp/g84.c
@@ -22,30 +22,20 @@
  * Authors: Ben Skeggs
  */
 #include "nv50.h"
+#include "head.h"
+#include "ior.h"
 #include "rootnv50.h"
 
 static const struct nv50_disp_func
 g84_disp = {
 	.intr = nv50_disp_intr,
 	.uevent = &nv50_disp_chan_uevent,
-	.super = nv50_disp_intr_supervisor,
+	.super = nv50_disp_super,
 	.root = &g84_disp_root_oclass,
-	.head.vblank_init = nv50_disp_vblank_init,
-	.head.vblank_fini = nv50_disp_vblank_fini,
-	.head.scanoutpos = nv50_disp_root_scanoutpos,
-	.outp.internal.crt = nv50_dac_output_new,
-	.outp.internal.tmds = nv50_sor_output_new,
-	.outp.internal.lvds = nv50_sor_output_new,
-	.outp.external.tmds = nv50_pior_output_new,
-	.outp.external.dp = nv50_pior_dp_new,
-	.dac.nr = 3,
-	.dac.power = nv50_dac_power,
-	.dac.sense = nv50_dac_sense,
-	.sor.nr = 2,
-	.sor.power = nv50_sor_power,
-	.sor.hdmi = g84_hdmi_ctrl,
-	.pior.nr = 3,
-	.pior.power = nv50_pior_power,
+	.head.new = nv50_head_new,
+	.dac = { .nr = 3, .new = nv50_dac_new },
+	.sor = { .nr = 2, .new = g84_sor_new },
+	.pior = { .nr = 3, .new = nv50_pior_new },
 };
 
 int
diff --git a/drivers/gpu/drm/nouveau/nvkm/engine/disp/g94.c b/drivers/gpu/drm/nouveau/nvkm/engine/disp/g94.c
index 7a7af3b478f8..d184e6ab8918 100644
--- a/drivers/gpu/drm/nouveau/nvkm/engine/disp/g94.c
+++ b/drivers/gpu/drm/nouveau/nvkm/engine/disp/g94.c
@@ -22,31 +22,20 @@
  * Authors: Ben Skeggs
  */
 #include "nv50.h"
+#include "head.h"
+#include "ior.h"
 #include "rootnv50.h"
 
 static const struct nv50_disp_func
 g94_disp = {
 	.intr = nv50_disp_intr,
 	.uevent = &nv50_disp_chan_uevent,
-	.super = nv50_disp_intr_supervisor,
+	.super = nv50_disp_super,
 	.root = &g94_disp_root_oclass,
-	.head.vblank_init = nv50_disp_vblank_init,
-	.head.vblank_fini = nv50_disp_vblank_fini,
-	.head.scanoutpos = nv50_disp_root_scanoutpos,
-	.outp.internal.crt = nv50_dac_output_new,
-	.outp.internal.tmds = nv50_sor_output_new,
-	.outp.internal.lvds = nv50_sor_output_new,
-	.outp.internal.dp = g94_sor_dp_new,
-	.outp.external.tmds = nv50_pior_output_new,
-	.outp.external.dp = nv50_pior_dp_new,
-	.dac.nr = 3,
-	.dac.power = nv50_dac_power,
-	.dac.sense = nv50_dac_sense,
-	.sor.nr = 4,
-	.sor.power = nv50_sor_power,
-	.sor.hdmi = g84_hdmi_ctrl,
-	.pior.nr = 3,
-	.pior.power = nv50_pior_power,
+	.head.new = nv50_head_new,
+	.dac = { .nr = 3, .new = nv50_dac_new },
+	.sor = { .nr = 4, .new = g94_sor_new },
+	.pior = { .nr = 3, .new = nv50_pior_new },
 };
 
 int
diff --git a/drivers/gpu/drm/nouveau/nvkm/engine/disp/gf119.c b/drivers/gpu/drm/nouveau/nvkm/engine/disp/gf119.c
index 7b346ccc38b7..d8765b57180b 100644
--- a/drivers/gpu/drm/nouveau/nvkm/engine/disp/gf119.c
+++ b/drivers/gpu/drm/nouveau/nvkm/engine/disp/gf119.c
@@ -22,397 +22,63 @@
  * Authors: Ben Skeggs
  */
 #include "nv50.h"
+#include "head.h"
+#include "ior.h"
 #include "rootnv50.h"
 
-#include <subdev/bios.h>
-#include <subdev/bios/disp.h>
-#include <subdev/bios/init.h>
-#include <subdev/bios/pll.h>
-#include <subdev/devinit.h>
-
-void
-gf119_disp_vblank_init(struct nv50_disp *disp, int head)
-{
-	struct nvkm_device *device = disp->base.engine.subdev.device;
-	nvkm_mask(device, 0x6100c0 + (head * 0x800), 0x00000001, 0x00000001);
-}
-
-void
-gf119_disp_vblank_fini(struct nv50_disp *disp, int head)
-{
-	struct nvkm_device *device = disp->base.engine.subdev.device;
-	nvkm_mask(device, 0x6100c0 + (head * 0x800), 0x00000001, 0x00000000);
-}
-
-static struct nvkm_output *
-exec_lookup(struct nv50_disp *disp, int head, int or, u32 ctrl,
-	    u32 *data, u8 *ver, u8 *hdr, u8 *cnt, u8 *len,
-	    struct nvbios_outp *info)
-{
-	struct nvkm_subdev *subdev = &disp->base.engine.subdev;
-	struct nvkm_bios *bios = subdev->device->bios;
-	struct nvkm_output *outp;
-	u16 mask, type;
-
-	if (or < 4) {
-		type = DCB_OUTPUT_ANALOG;
-		mask = 0;
-	} else {
-		or -= 4;
-		switch (ctrl & 0x00000f00) {
-		case 0x00000000: type = DCB_OUTPUT_LVDS; mask = 1; break;
-		case 0x00000100: type = DCB_OUTPUT_TMDS; mask = 1; break;
-		case 0x00000200: type = DCB_OUTPUT_TMDS; mask = 2; break;
-		case 0x00000500: type = DCB_OUTPUT_TMDS; mask = 3; break;
-		case 0x00000800: type = DCB_OUTPUT_DP; mask = 1; break;
-		case 0x00000900: type = DCB_OUTPUT_DP; mask = 2; break;
-		default:
-			nvkm_error(subdev, "unknown SOR mc %08x\n", ctrl);
-			return NULL;
-		}
-	}
-
-	mask  = 0x00c0 & (mask << 6);
-	mask |= 0x0001 << or;
-	mask |= 0x0100 << head;
-
-	list_for_each_entry(outp, &disp->base.outp, head) {
-		if ((outp->info.hasht & 0xff) == type &&
-		    (outp->info.hashm & mask) == mask) {
-			*data = nvbios_outp_match(bios, outp->info.hasht, mask,
-						  ver, hdr, cnt, len, info);
-			if (!*data)
-				return NULL;
-			return outp;
-		}
-	}
-
-	return NULL;
-}
-
-static struct nvkm_output *
-exec_script(struct nv50_disp *disp, int head, int id)
-{
-	struct nvkm_subdev *subdev = &disp->base.engine.subdev;
-	struct nvkm_device *device = subdev->device;
-	struct nvkm_bios *bios = device->bios;
-	struct nvkm_output *outp;
-	struct nvbios_outp info;
-	u8  ver, hdr, cnt, len;
-	u32 data, ctrl = 0;
-	int or;
-
-	for (or = 0; !(ctrl & (1 << head)) && or < 8; or++) {
-		ctrl = nvkm_rd32(device, 0x640180 + (or * 0x20));
-		if (ctrl & (1 << head))
-			break;
-	}
-
-	if (or == 8)
-		return NULL;
-
-	outp = exec_lookup(disp, head, or, ctrl, &data, &ver, &hdr, &cnt, &len, &info);
-	if (outp) {
-		struct nvbios_init init = {
-			.subdev = subdev,
-			.bios = bios,
-			.offset = info.script[id],
-			.outp = &outp->info,
-			.crtc = head,
-			.execute = 1,
-		};
-
-		nvbios_exec(&init);
-	}
-
-	return outp;
-}
-
-static struct nvkm_output *
-exec_clkcmp(struct nv50_disp *disp, int head, int id, u32 pclk, u32 *conf)
-{
-	struct nvkm_subdev *subdev = &disp->base.engine.subdev;
-	struct nvkm_device *device = subdev->device;
-	struct nvkm_bios *bios = device->bios;
-	struct nvkm_output *outp;
-	struct nvbios_outp info1;
-	struct nvbios_ocfg info2;
-	u8  ver, hdr, cnt, len;
-	u32 data, ctrl = 0;
-	int or;
-
-	for (or = 0; !(ctrl & (1 << head)) && or < 8; or++) {
-		ctrl = nvkm_rd32(device, 0x660180 + (or * 0x20));
-		if (ctrl & (1 << head))
-			break;
-	}
-
-	if (or == 8)
-		return NULL;
-
-	outp = exec_lookup(disp, head, or, ctrl, &data, &ver, &hdr, &cnt, &len, &info1);
-	if (!outp)
-		return NULL;
-
-	*conf = (ctrl & 0x00000f00) >> 8;
-	switch (outp->info.type) {
-	case DCB_OUTPUT_TMDS:
-		if (*conf == 5)
-			*conf |= 0x0100;
-		break;
-	case DCB_OUTPUT_LVDS:
-		*conf |= disp->sor.lvdsconf;
-		break;
-	default:
-		break;
-	}
-
-	data = nvbios_ocfg_match(bios, data, *conf & 0xff, *conf >> 8,
-				 &ver, &hdr, &cnt, &len, &info2);
-	if (data && id < 0xff) {
-		data = nvbios_oclk_match(bios, info2.clkcmp[id], pclk);
-		if (data) {
-			struct nvbios_init init = {
-				.subdev = subdev,
-				.bios = bios,
-				.offset = data,
-				.outp = &outp->info,
-				.crtc = head,
-				.execute = 1,
-			};
-
-			nvbios_exec(&init);
-		}
-	}
-
-	return outp;
-}
-
-static void
-gf119_disp_intr_unk1_0(struct nv50_disp *disp, int head)
-{
-	exec_script(disp, head, 1);
-}
-
-static void
-gf119_disp_intr_unk2_0(struct nv50_disp *disp, int head)
-{
-	struct nvkm_subdev *subdev = &disp->base.engine.subdev;
-	struct nvkm_output *outp = exec_script(disp, head, 2);
-
-	/* see note in nv50_disp_intr_unk20_0() */
-	if (outp && outp->info.type == DCB_OUTPUT_DP) {
-		struct nvkm_output_dp *outpdp = nvkm_output_dp(outp);
-		if (!outpdp->lt.mst) {
-			struct nvbios_init init = {
-				.subdev = subdev,
-				.bios = subdev->device->bios,
-				.outp = &outp->info,
-				.crtc = head,
-				.offset = outpdp->info.script[4],
-				.execute = 1,
-			};
-
-			nvkm_notify_put(&outpdp->irq);
-			nvbios_exec(&init);
-			atomic_set(&outpdp->lt.done, 0);
-		}
-	}
-}
-
-static void
-gf119_disp_intr_unk2_1(struct nv50_disp *disp, int head)
-{
-	struct nvkm_device *device = disp->base.engine.subdev.device;
-	struct nvkm_devinit *devinit = device->devinit;
-	u32 pclk = nvkm_rd32(device, 0x660450 + (head * 0x300)) / 1000;
-	if (pclk)
-		nvkm_devinit_pll_set(devinit, PLL_VPLL0 + head, pclk);
-	nvkm_wr32(device, 0x612200 + (head * 0x800), 0x00000000);
-}
-
-static void
-gf119_disp_intr_unk2_2_tu(struct nv50_disp *disp, int head,
-			  struct dcb_output *outp)
-{
-	struct nvkm_device *device = disp->base.engine.subdev.device;
-	const int or = ffs(outp->or) - 1;
-	const u32 ctrl = nvkm_rd32(device, 0x660200 + (or   * 0x020));
-	const u32 conf = nvkm_rd32(device, 0x660404 + (head * 0x300));
-	const s32 vactive = nvkm_rd32(device, 0x660414 + (head * 0x300)) & 0xffff;
-	const s32 vblanke = nvkm_rd32(device, 0x66041c + (head * 0x300)) & 0xffff;
-	const s32 vblanks = nvkm_rd32(device, 0x660420 + (head * 0x300)) & 0xffff;
-	const u32 pclk = nvkm_rd32(device, 0x660450 + (head * 0x300)) / 1000;
-	const u32 link = ((ctrl & 0xf00) == 0x800) ? 0 : 1;
-	const u32 hoff = (head * 0x800);
-	const u32 soff = (  or * 0x800);
-	const u32 loff = (link * 0x080) + soff;
-	const u32 symbol = 100000;
-	const u32 TU = 64;
-	u32 dpctrl = nvkm_rd32(device, 0x61c10c + loff);
-	u32 clksor = nvkm_rd32(device, 0x612300 + soff);
-	u32 datarate, link_nr, link_bw, bits;
-	u64 ratio, value;
-
-	link_nr  = hweight32(dpctrl & 0x000f0000);
-	link_bw  = (clksor & 0x007c0000) >> 18;
-	link_bw *= 27000;
-
-	/* symbols/hblank - algorithm taken from comments in tegra driver */
-	value = vblanke + vactive - vblanks - 7;
-	value = value * link_bw;
-	do_div(value, pclk);
-	value = value - (3 * !!(dpctrl & 0x00004000)) - (12 / link_nr);
-	nvkm_mask(device, 0x616620 + hoff, 0x0000ffff, value);
-
-	/* symbols/vblank - algorithm taken from comments in tegra driver */
-	value = vblanks - vblanke - 25;
-	value = value * link_bw;
-	do_div(value, pclk);
-	value = value - ((36 / link_nr) + 3) - 1;
-	nvkm_mask(device, 0x616624 + hoff, 0x00ffffff, value);
-
-	/* watermark */
-	if      ((conf & 0x3c0) == 0x180) bits = 30;
-	else if ((conf & 0x3c0) == 0x140) bits = 24;
-	else                              bits = 18;
-	datarate = (pclk * bits) / 8;
-
-	ratio  = datarate;
-	ratio *= symbol;
-	do_div(ratio, link_nr * link_bw);
-
-	value  = (symbol - ratio) * TU;
-	value *= ratio;
-	do_div(value, symbol);
-	do_div(value, symbol);
-
-	value += 5;
-	value |= 0x08000000;
-
-	nvkm_wr32(device, 0x616610 + hoff, value);
-}
-
-static void
-gf119_disp_intr_unk2_2(struct nv50_disp *disp, int head)
-{
-	struct nvkm_device *device = disp->base.engine.subdev.device;
-	struct nvkm_output *outp;
-	u32 pclk = nvkm_rd32(device, 0x660450 + (head * 0x300)) / 1000;
-	u32 conf, addr, data;
-
-	outp = exec_clkcmp(disp, head, 0xff, pclk, &conf);
-	if (!outp)
-		return;
-
-	/* see note in nv50_disp_intr_unk20_2() */
-	if (outp->info.type == DCB_OUTPUT_DP) {
-		u32 sync = nvkm_rd32(device, 0x660404 + (head * 0x300));
-		switch ((sync & 0x000003c0) >> 6) {
-		case 6: pclk = pclk * 30; break;
-		case 5: pclk = pclk * 24; break;
-		case 2:
-		default:
-			pclk = pclk * 18;
-			break;
-		}
-
-		if (nvkm_output_dp_train(outp, pclk))
-			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);
-
-	if (outp->info.type == DCB_OUTPUT_ANALOG) {
-		addr = 0x612280 + (ffs(outp->info.or) - 1) * 0x800;
-		data = 0x00000000;
-	} else {
-		addr = 0x612300 + (ffs(outp->info.or) - 1) * 0x800;
-		data = (conf & 0x0100) ? 0x00000101 : 0x00000000;
-		switch (outp->info.type) {
-		case DCB_OUTPUT_TMDS:
-			nvkm_mask(device, addr, 0x007c0000, 0x00280000);
-			break;
-		case DCB_OUTPUT_DP:
-			gf119_disp_intr_unk2_2_tu(disp, head, &outp->info);
-			break;
-		default:
-			break;
-		}
-	}
-
-	nvkm_mask(device, addr, 0x00000707, data);
-}
-
-static void
-gf119_disp_intr_unk4_0(struct nv50_disp *disp, int head)
-{
-	struct nvkm_device *device = disp->base.engine.subdev.device;
-	u32 pclk = nvkm_rd32(device, 0x660450 + (head * 0x300)) / 1000;
-	u32 conf;
-
-	exec_clkcmp(disp, head, 1, pclk, &conf);
-}
-
 void
-gf119_disp_intr_supervisor(struct work_struct *work)
+gf119_disp_super(struct work_struct *work)
 {
 	struct nv50_disp *disp =
 		container_of(work, struct nv50_disp, supervisor);
 	struct nvkm_subdev *subdev = &disp->base.engine.subdev;
 	struct nvkm_device *device = subdev->device;
+	struct nvkm_head *head;
 	u32 mask[4];
-	int head;
 
 	nvkm_debug(subdev, "supervisor %d\n", ffs(disp->super));
-	for (head = 0; head < disp->base.head.nr; head++) {
-		mask[head] = nvkm_rd32(device, 0x6101d4 + (head * 0x800));
-		nvkm_debug(subdev, "head %d: %08x\n", head, mask[head]);
+	list_for_each_entry(head, &disp->base.head, head) {
+		mask[head->id] = nvkm_rd32(device, 0x6101d4 + (head->id * 0x800));
+		HEAD_DBG(head, "%08x", mask[head->id]);
 	}
 
 	if (disp->super & 0x00000001) {
 		nv50_disp_chan_mthd(disp->chan[0], NV_DBG_DEBUG);
-		for (head = 0; head < disp->base.head.nr; head++) {
-			if (!(mask[head] & 0x00001000))
+		nv50_disp_super_1(disp);
+		list_for_each_entry(head, &disp->base.head, head) {
+			if (!(mask[head->id] & 0x00001000))
 				continue;
-			nvkm_debug(subdev, "supervisor 1.0 - head %d\n", head);
-			gf119_disp_intr_unk1_0(disp, head);
+			nv50_disp_super_1_0(disp, head);
 		}
 	} else
 	if (disp->super & 0x00000002) {
-		for (head = 0; head < disp->base.head.nr; head++) {
-			if (!(mask[head] & 0x00001000))
+		list_for_each_entry(head, &disp->base.head, head) {
+			if (!(mask[head->id] & 0x00001000))
 				continue;
-			nvkm_debug(subdev, "supervisor 2.0 - head %d\n", head);
-			gf119_disp_intr_unk2_0(disp, head);
+			nv50_disp_super_2_0(disp, head);
 		}
-		for (head = 0; head < disp->base.head.nr; head++) {
-			if (!(mask[head] & 0x00010000))
+		nvkm_outp_route(&disp->base);
+		list_for_each_entry(head, &disp->base.head, head) {
+			if (!(mask[head->id] & 0x00010000))
 				continue;
-			nvkm_debug(subdev, "supervisor 2.1 - head %d\n", head);
-			gf119_disp_intr_unk2_1(disp, head);
+			nv50_disp_super_2_1(disp, head);
 		}
-		for (head = 0; head < disp->base.head.nr; head++) {
-			if (!(mask[head] & 0x00001000))
+		list_for_each_entry(head, &disp->base.head, head) {
+			if (!(mask[head->id] & 0x00001000))
 				continue;
-			nvkm_debug(subdev, "supervisor 2.2 - head %d\n", head);
-			gf119_disp_intr_unk2_2(disp, head);
+			nv50_disp_super_2_2(disp, head);
 		}
 	} else
 	if (disp->super & 0x00000004) {
-		for (head = 0; head < disp->base.head.nr; head++) {
-			if (!(mask[head] & 0x00001000))
+		list_for_each_entry(head, &disp->base.head, head) {
+			if (!(mask[head->id] & 0x00001000))
 				continue;
-			nvkm_debug(subdev, "supervisor 3.0 - head %d\n", head);
-			gf119_disp_intr_unk4_0(disp, head);
+			nv50_disp_super_3_0(disp, head);
 		}
 	}
 
-	for (head = 0; head < disp->base.head.nr; head++)
-		nvkm_wr32(device, 0x6101d4 + (head * 0x800), 0x00000000);
+	list_for_each_entry(head, &disp->base.head, head)
+		nvkm_wr32(device, 0x6101d4 + (head->id * 0x800), 0x00000000);
 	nvkm_wr32(device, 0x6101d0, 0x80000000);
 }
 
@@ -447,8 +113,8 @@ gf119_disp_intr(struct nv50_disp *disp)
 {
 	struct nvkm_subdev *subdev = &disp->base.engine.subdev;
 	struct nvkm_device *device = subdev->device;
+	struct nvkm_head *head;
 	u32 intr = nvkm_rd32(device, 0x610088);
-	int i;
 
 	if (intr & 0x00000001) {
 		u32 stat = nvkm_rd32(device, 0x61008c);
@@ -472,7 +138,7 @@ gf119_disp_intr(struct nv50_disp *disp)
 		u32 stat = nvkm_rd32(device, 0x6100ac);
 		if (stat & 0x00000007) {
 			disp->super = (stat & 0x00000007);
-			schedule_work(&disp->supervisor);
+			queue_work(disp->wq, &disp->supervisor);
 			nvkm_wr32(device, 0x6100ac, disp->super);
 			stat &= ~0x00000007;
 		}
@@ -485,14 +151,15 @@ gf119_disp_intr(struct nv50_disp *disp)
 		intr &= ~0x00100000;
 	}
 
-	for (i = 0; i < disp->base.head.nr; i++) {
-		u32 mask = 0x01000000 << i;
+	list_for_each_entry(head, &disp->base.head, head) {
+		const u32 hoff = head->id * 0x800;
+		u32 mask = 0x01000000 << head->id;
 		if (mask & intr) {
-			u32 stat = nvkm_rd32(device, 0x6100bc + (i * 0x800));
+			u32 stat = nvkm_rd32(device, 0x6100bc + hoff);
 			if (stat & 0x00000001)
-				nvkm_disp_vblank(&disp->base, i);
-			nvkm_mask(device, 0x6100bc + (i * 0x800), 0, 0);
-			nvkm_rd32(device, 0x6100c0 + (i * 0x800));
+				nvkm_disp_vblank(&disp->base, head->id);
+			nvkm_mask(device, 0x6100bc + hoff, 0, 0);
+			nvkm_rd32(device, 0x6100c0 + hoff);
 		}
 	}
 }
@@ -510,22 +177,11 @@ gf119_disp = {
 	.intr = gf119_disp_intr,
 	.intr_error = gf119_disp_intr_error,
 	.uevent = &gf119_disp_chan_uevent,
-	.super = gf119_disp_intr_supervisor,
+	.super = gf119_disp_super,
 	.root = &gf119_disp_root_oclass,
-	.head.vblank_init = gf119_disp_vblank_init,
-	.head.vblank_fini = gf119_disp_vblank_fini,
-	.head.scanoutpos = gf119_disp_root_scanoutpos,
-	.outp.internal.crt = nv50_dac_output_new,
-	.outp.internal.tmds = nv50_sor_output_new,
-	.outp.internal.lvds = nv50_sor_output_new,
-	.outp.internal.dp = gf119_sor_dp_new,
-	.dac.nr = 3,
-	.dac.power = nv50_dac_power,
-	.dac.sense = nv50_dac_sense,
-	.sor.nr = 4,
-	.sor.power = nv50_sor_power,
-	.sor.hda_eld = gf119_hda_eld,
-	.sor.hdmi = gf119_hdmi_ctrl,
+	.head.new = gf119_head_new,
+	.dac = { .nr = 3, .new = gf119_dac_new },
+	.sor = { .nr = 4, .new = gf119_sor_new },
 };
 
 int
diff --git a/drivers/gpu/drm/nouveau/nvkm/engine/disp/gk104.c b/drivers/gpu/drm/nouveau/nvkm/engine/disp/gk104.c
index 37f145cf30d7..e8fe9f315d64 100644
--- a/drivers/gpu/drm/nouveau/nvkm/engine/disp/gk104.c
+++ b/drivers/gpu/drm/nouveau/nvkm/engine/disp/gk104.c
@@ -22,6 +22,8 @@
  * Authors: Ben Skeggs
  */
 #include "nv50.h"
+#include "head.h"
+#include "ior.h"
 #include "rootnv50.h"
 
 static const struct nv50_disp_func
@@ -29,22 +31,11 @@ gk104_disp = {
 	.intr = gf119_disp_intr,
 	.intr_error = gf119_disp_intr_error,
 	.uevent = &gf119_disp_chan_uevent,
-	.super = gf119_disp_intr_supervisor,
+	.super = gf119_disp_super,
 	.root = &gk104_disp_root_oclass,
-	.head.vblank_init = gf119_disp_vblank_init,
-	.head.vblank_fini = gf119_disp_vblank_fini,
-	.head.scanoutpos = gf119_disp_root_scanoutpos,
-	.outp.internal.crt = nv50_dac_output_new,
-	.outp.internal.tmds = nv50_sor_output_new,
-	.outp.internal.lvds = nv50_sor_output_new,
-	.outp.internal.dp = gf119_sor_dp_new,
-	.dac.nr = 3,
-	.dac.power = nv50_dac_power,
-	.dac.sense = nv50_dac_sense,
-	.sor.nr = 4,
-	.sor.power = nv50_sor_power,
-	.sor.hda_eld = gf119_hda_eld,
-	.sor.hdmi = gk104_hdmi_ctrl,
+	.head.new = gf119_head_new,
+	.dac = { .nr = 3, .new = gf119_dac_new },
+	.sor = { .nr = 4, .new = gk104_sor_new },
 };
 
 int
diff --git a/drivers/gpu/drm/nouveau/nvkm/engine/disp/gk110.c b/drivers/gpu/drm/nouveau/nvkm/engine/disp/gk110.c
index e14ac946608c..769687502e7a 100644
--- a/drivers/gpu/drm/nouveau/nvkm/engine/disp/gk110.c
+++ b/drivers/gpu/drm/nouveau/nvkm/engine/disp/gk110.c
@@ -22,6 +22,8 @@
  * Authors: Ben Skeggs
  */
 #include "nv50.h"
+#include "head.h"
+#include "ior.h"
 #include "rootnv50.h"
 
 static const struct nv50_disp_func
@@ -29,22 +31,11 @@ gk110_disp = {
 	.intr = gf119_disp_intr,
 	.intr_error = gf119_disp_intr_error,
 	.uevent = &gf119_disp_chan_uevent,
-	.super = gf119_disp_intr_supervisor,
+	.super = gf119_disp_super,
 	.root = &gk110_disp_root_oclass,
-	.head.vblank_init = gf119_disp_vblank_init,
-	.head.vblank_fini = gf119_disp_vblank_fini,
-	.head.scanoutpos = gf119_disp_root_scanoutpos,
-	.outp.internal.crt = nv50_dac_output_new,
-	.outp.internal.tmds = nv50_sor_output_new,
-	.outp.internal.lvds = nv50_sor_output_new,
-	.outp.internal.dp = gf119_sor_dp_new,
-	.dac.nr = 3,
-	.dac.power = nv50_dac_power,
-	.dac.sense = nv50_dac_sense,
-	.sor.nr = 4,
-	.sor.power = nv50_sor_power,
-	.sor.hda_eld = gf119_hda_eld,
-	.sor.hdmi = gk104_hdmi_ctrl,
+	.head.new = gf119_head_new,
+	.dac = { .nr = 3, .new = gf119_dac_new },
+	.sor = { .nr = 4, .new = gk104_sor_new },
 };
 
 int
diff --git a/drivers/gpu/drm/nouveau/nvkm/engine/disp/gm107.c b/drivers/gpu/drm/nouveau/nvkm/engine/disp/gm107.c
index 2f2437cc5891..ede70e5d188e 100644
--- a/drivers/gpu/drm/nouveau/nvkm/engine/disp/gm107.c
+++ b/drivers/gpu/drm/nouveau/nvkm/engine/disp/gm107.c
@@ -22,6 +22,8 @@
  * Authors: Ben Skeggs
  */
 #include "nv50.h"
+#include "head.h"
+#include "ior.h"
 #include "rootnv50.h"
 
 static const struct nv50_disp_func
@@ -29,22 +31,11 @@ gm107_disp = {
 	.intr = gf119_disp_intr,
 	.intr_error = gf119_disp_intr_error,
 	.uevent = &gf119_disp_chan_uevent,
-	.super = gf119_disp_intr_supervisor,
+	.super = gf119_disp_super,
 	.root = &gm107_disp_root_oclass,
-	.head.vblank_init = gf119_disp_vblank_init,
-	.head.vblank_fini = gf119_disp_vblank_fini,
-	.head.scanoutpos = gf119_disp_root_scanoutpos,
-	.outp.internal.crt = nv50_dac_output_new,
-	.outp.internal.tmds = nv50_sor_output_new,
-	.outp.internal.lvds = nv50_sor_output_new,
-	.outp.internal.dp = gm107_sor_dp_new,
-	.dac.nr = 3,
-	.dac.power = nv50_dac_power,
-	.dac.sense = nv50_dac_sense,
-	.sor.nr = 4,
-	.sor.power = nv50_sor_power,
-	.sor.hda_eld = gf119_hda_eld,
-	.sor.hdmi = gk104_hdmi_ctrl,
+	.head.new = gf119_head_new,
+	.dac = { .nr = 3, .new = gf119_dac_new },
+	.sor = { .nr = 4, .new = gm107_sor_new },
 };
 
 int
diff --git a/drivers/gpu/drm/nouveau/nvkm/engine/disp/gm200.c b/drivers/gpu/drm/nouveau/nvkm/engine/disp/gm200.c
index 9f368d4ee61e..292d3b5f9704 100644
--- a/drivers/gpu/drm/nouveau/nvkm/engine/disp/gm200.c
+++ b/drivers/gpu/drm/nouveau/nvkm/engine/disp/gm200.c
@@ -22,6 +22,8 @@
  * Authors: Ben Skeggs
  */
 #include "nv50.h"
+#include "head.h"
+#include "ior.h"
 #include "rootnv50.h"
 
 static const struct nv50_disp_func
@@ -29,23 +31,11 @@ gm200_disp = {
 	.intr = gf119_disp_intr,
 	.intr_error = gf119_disp_intr_error,
 	.uevent = &gf119_disp_chan_uevent,
-	.super = gf119_disp_intr_supervisor,
+	.super = gf119_disp_super,
 	.root = &gm200_disp_root_oclass,
-	.head.vblank_init = gf119_disp_vblank_init,
-	.head.vblank_fini = gf119_disp_vblank_fini,
-	.head.scanoutpos = gf119_disp_root_scanoutpos,
-	.outp.internal.crt = nv50_dac_output_new,
-	.outp.internal.tmds = nv50_sor_output_new,
-	.outp.internal.lvds = nv50_sor_output_new,
-	.outp.internal.dp = gm200_sor_dp_new,
-	.dac.nr = 3,
-	.dac.power = nv50_dac_power,
-	.dac.sense = nv50_dac_sense,
-	.sor.nr = 4,
-	.sor.power = nv50_sor_power,
-	.sor.hda_eld = gf119_hda_eld,
-	.sor.hdmi = gk104_hdmi_ctrl,
-	.sor.magic = gm200_sor_magic,
+	.head.new = gf119_head_new,
+	.dac = { .nr = 3, .new = gf119_dac_new },
+	.sor = { .nr = 4, .new = gm200_sor_new },
 };
 
 int
diff --git a/drivers/gpu/drm/nouveau/nvkm/engine/disp/gp100.c b/drivers/gpu/drm/nouveau/nvkm/engine/disp/gp100.c
index 4f81bf31435e..39eb98b2c3a2 100644
--- a/drivers/gpu/drm/nouveau/nvkm/engine/disp/gp100.c
+++ b/drivers/gpu/drm/nouveau/nvkm/engine/disp/gp100.c
@@ -22,6 +22,8 @@
  * Authors: Ben Skeggs <bskeggs@redhat.com>
  */
 #include "nv50.h"
+#include "head.h"
+#include "ior.h"
 #include "rootnv50.h"
 
 static const struct nv50_disp_func
@@ -29,23 +31,10 @@ gp100_disp = {
 	.intr = gf119_disp_intr,
 	.intr_error = gf119_disp_intr_error,
 	.uevent = &gf119_disp_chan_uevent,
-	.super = gf119_disp_intr_supervisor,
+	.super = gf119_disp_super,
 	.root = &gp100_disp_root_oclass,
-	.head.vblank_init = gf119_disp_vblank_init,
-	.head.vblank_fini = gf119_disp_vblank_fini,
-	.head.scanoutpos = gf119_disp_root_scanoutpos,
-	.outp.internal.crt = nv50_dac_output_new,
-	.outp.internal.tmds = nv50_sor_output_new,
-	.outp.internal.lvds = nv50_sor_output_new,
-	.outp.internal.dp = gm200_sor_dp_new,
-	.dac.nr = 3,
-	.dac.power = nv50_dac_power,
-	.dac.sense = nv50_dac_sense,
-	.sor.nr = 4,
-	.sor.power = nv50_sor_power,
-	.sor.hda_eld = gf119_hda_eld,
-	.sor.hdmi = gk104_hdmi_ctrl,
-	.sor.magic = gm200_sor_magic,
+	.head.new = gf119_head_new,
+	.sor = { .nr = 4, .new = gm200_sor_new },
 };
 
 int
diff --git a/drivers/gpu/drm/nouveau/nvkm/engine/disp/gp102.c b/drivers/gpu/drm/nouveau/nvkm/engine/disp/gp102.c
index f5d613f82709..91d70fe18275 100644
--- a/drivers/gpu/drm/nouveau/nvkm/engine/disp/gp102.c
+++ b/drivers/gpu/drm/nouveau/nvkm/engine/disp/gp102.c
@@ -22,6 +22,8 @@
  * Authors: Ben Skeggs <bskeggs@redhat.com>
  */
 #include "nv50.h"
+#include "head.h"
+#include "ior.h"
 #include "rootnv50.h"
 
 static void
@@ -55,23 +57,10 @@ gp102_disp = {
 	.intr = gf119_disp_intr,
 	.intr_error = gp102_disp_intr_error,
 	.uevent = &gf119_disp_chan_uevent,
-	.super = gf119_disp_intr_supervisor,
+	.super = gf119_disp_super,
 	.root = &gp102_disp_root_oclass,
-	.head.vblank_init = gf119_disp_vblank_init,
-	.head.vblank_fini = gf119_disp_vblank_fini,
-	.head.scanoutpos = gf119_disp_root_scanoutpos,
-	.outp.internal.crt = nv50_dac_output_new,
-	.outp.internal.tmds = nv50_sor_output_new,
-	.outp.internal.lvds = nv50_sor_output_new,
-	.outp.internal.dp = gm200_sor_dp_new,
-	.dac.nr = 3,
-	.dac.power = nv50_dac_power,
-	.dac.sense = nv50_dac_sense,
-	.sor.nr = 4,
-	.sor.power = nv50_sor_power,
-	.sor.hda_eld = gf119_hda_eld,
-	.sor.hdmi = gk104_hdmi_ctrl,
-	.sor.magic = gm200_sor_magic,
+	.head.new = gf119_head_new,
+	.sor = { .nr = 4, .new = gm200_sor_new },
 };
 
 int
diff --git a/drivers/gpu/drm/nouveau/nvkm/engine/disp/gt200.c b/drivers/gpu/drm/nouveau/nvkm/engine/disp/gt200.c
index 6bc3bf096001..bf00c4e3be3a 100644
--- a/drivers/gpu/drm/nouveau/nvkm/engine/disp/gt200.c
+++ b/drivers/gpu/drm/nouveau/nvkm/engine/disp/gt200.c
@@ -22,30 +22,20 @@
  * Authors: Ben Skeggs
  */
 #include "nv50.h"
+#include "head.h"
+#include "ior.h"
 #include "rootnv50.h"
 
 static const struct nv50_disp_func
 gt200_disp = {
 	.intr = nv50_disp_intr,
 	.uevent = &nv50_disp_chan_uevent,
-	.super = nv50_disp_intr_supervisor,
+	.super = nv50_disp_super,
 	.root = &gt200_disp_root_oclass,
-	.head.vblank_init = nv50_disp_vblank_init,
-	.head.vblank_fini = nv50_disp_vblank_fini,
-	.head.scanoutpos = nv50_disp_root_scanoutpos,
-	.outp.internal.crt = nv50_dac_output_new,
-	.outp.internal.tmds = nv50_sor_output_new,
-	.outp.internal.lvds = nv50_sor_output_new,
-	.outp.external.tmds = nv50_pior_output_new,
-	.outp.external.dp = nv50_pior_dp_new,
-	.dac.nr = 3,
-	.dac.power = nv50_dac_power,
-	.dac.sense = nv50_dac_sense,
-	.sor.nr = 2,
-	.sor.power = nv50_sor_power,
-	.sor.hdmi = g84_hdmi_ctrl,
-	.pior.nr = 3,
-	.pior.power = nv50_pior_power,
+	.head.new = nv50_head_new,
+	.dac = { .nr = 3, .new = nv50_dac_new },
+	.sor = { .nr = 2, .new = g84_sor_new },
+	.pior = { .nr = 3, .new = nv50_pior_new },
 };
 
 int
diff --git a/drivers/gpu/drm/nouveau/nvkm/engine/disp/gt215.c b/drivers/gpu/drm/nouveau/nvkm/engine/disp/gt215.c
index 94026288ab4d..2cdd4d7a98d3 100644
--- a/drivers/gpu/drm/nouveau/nvkm/engine/disp/gt215.c
+++ b/drivers/gpu/drm/nouveau/nvkm/engine/disp/gt215.c
@@ -22,32 +22,20 @@
  * Authors: Ben Skeggs
  */
 #include "nv50.h"
+#include "head.h"
+#include "ior.h"
 #include "rootnv50.h"
 
 static const struct nv50_disp_func
 gt215_disp = {
 	.intr = nv50_disp_intr,
 	.uevent = &nv50_disp_chan_uevent,
-	.super = nv50_disp_intr_supervisor,
+	.super = nv50_disp_super,
 	.root = &gt215_disp_root_oclass,
-	.head.vblank_init = nv50_disp_vblank_init,
-	.head.vblank_fini = nv50_disp_vblank_fini,
-	.head.scanoutpos = nv50_disp_root_scanoutpos,
-	.outp.internal.crt = nv50_dac_output_new,
-	.outp.internal.tmds = nv50_sor_output_new,
-	.outp.internal.lvds = nv50_sor_output_new,
-	.outp.internal.dp = g94_sor_dp_new,
-	.outp.external.tmds = nv50_pior_output_new,
-	.outp.external.dp = nv50_pior_dp_new,
-	.dac.nr = 3,
-	.dac.power = nv50_dac_power,
-	.dac.sense = nv50_dac_sense,
-	.sor.nr = 4,
-	.sor.power = nv50_sor_power,
-	.sor.hda_eld = gt215_hda_eld,
-	.sor.hdmi = gt215_hdmi_ctrl,
-	.pior.nr = 3,
-	.pior.power = nv50_pior_power,
+	.head.new = nv50_head_new,
+	.dac = { .nr = 3, .new = nv50_dac_new },
+	.sor = { .nr = 4, .new = gt215_sor_new },
+	.pior = { .nr = 3, .new = nv50_pior_new },
 };
 
 int
diff --git a/drivers/gpu/drm/nouveau/nvkm/engine/disp/hdagf119.c b/drivers/gpu/drm/nouveau/nvkm/engine/disp/hdagf119.c
index da6129b2b78f..0fa0ec0a1de0 100644
--- a/drivers/gpu/drm/nouveau/nvkm/engine/disp/hdagf119.c
+++ b/drivers/gpu/drm/nouveau/nvkm/engine/disp/hdagf119.c
@@ -21,63 +21,34 @@
  *
  * Authors: Ben Skeggs
  */
-#include "nv50.h"
-#include "outp.h"
+#include "ior.h"
 
-#include <core/client.h>
-#include <subdev/bios.h>
-#include <subdev/bios/dcb.h>
-#include <subdev/timer.h>
-
-#include <nvif/cl5070.h>
-#include <nvif/unpack.h>
-
-int
-gf119_hda_eld(NV50_DISP_MTHD_V1)
+void
+gf119_hda_eld(struct nvkm_ior *ior, u8 *data, u8 size)
 {
-	struct nvkm_device *device = disp->base.engine.subdev.device;
-	union {
-		struct nv50_disp_sor_hda_eld_v0 v0;
-	} *args = data;
-	const u32 soff = outp->or * 0x030;
-	const u32 hoff = head * 0x800;
-	int ret = -ENOSYS, i;
+	struct nvkm_device *device = ior->disp->engine.subdev.device;
+	const u32 soff = 0x030 * ior->id;
+	int i;
 
-	nvif_ioctl(object, "disp sor hda eld size %d\n", size);
-	if (!(ret = nvif_unpack(ret, &data, &size, args->v0, 0, 0, true))) {
-		nvif_ioctl(object, "disp sor hda eld vers %d\n",
-			   args->v0.version);
-		if (size > 0x60)
-			return -E2BIG;
-	} else
-		return ret;
+	for (i = 0; i < size; i++)
+		nvkm_wr32(device, 0x10ec00 + soff, (i << 8) | data[i]);
+	for (; i < 0x60; i++)
+		nvkm_wr32(device, 0x10ec00 + soff, (i << 8));
+	nvkm_mask(device, 0x10ec10 + soff, 0x80000002, 0x80000002);
+}
 
-	if (size && args->v0.data[0]) {
-		if (outp->info.type == DCB_OUTPUT_DP) {
-			nvkm_mask(device, 0x616618 + hoff, 0x8000000c, 0x80000001);
-			nvkm_msec(device, 2000,
-				u32 tmp = nvkm_rd32(device, 0x616618 + hoff);
-				if (!(tmp & 0x80000000))
-					break;
-			);
-		}
+void
+gf119_hda_hpd(struct nvkm_ior *ior, int head, bool present)
+{
+	struct nvkm_device *device = ior->disp->engine.subdev.device;
+	const u32 hoff = 0x800 * head;
+	u32 data = 0x80000000;
+	u32 mask = 0x80000001;
+	if (present) {
 		nvkm_mask(device, 0x616548 + hoff, 0x00000070, 0x00000000);
-		for (i = 0; i < size; i++)
-			nvkm_wr32(device, 0x10ec00 + soff, (i << 8) | args->v0.data[i]);
-		for (; i < 0x60; i++)
-			nvkm_wr32(device, 0x10ec00 + soff, (i << 8));
-		nvkm_mask(device, 0x10ec10 + soff, 0x80000003, 0x80000003);
+		data |= 0x00000001;
 	} else {
-		if (outp->info.type == DCB_OUTPUT_DP) {
-			nvkm_mask(device, 0x616618 + hoff, 0x80000001, 0x80000000);
-			nvkm_msec(device, 2000,
-				u32 tmp = nvkm_rd32(device, 0x616618 + hoff);
-				if (!(tmp & 0x80000000))
-					break;
-			);
-		}
-		nvkm_mask(device, 0x10ec10 + soff, 0x80000003, 0x80000000 | !!size);
+		mask |= 0x00000002;
 	}
-
-	return 0;
+	nvkm_mask(device, 0x10ec10 + ior->id * 0x030, mask, data);
 }
diff --git a/drivers/gpu/drm/nouveau/nvkm/engine/disp/hdagt215.c b/drivers/gpu/drm/nouveau/nvkm/engine/disp/hdagt215.c
index f8f2f16c22a2..4509d2ba880e 100644
--- a/drivers/gpu/drm/nouveau/nvkm/engine/disp/hdagt215.c
+++ b/drivers/gpu/drm/nouveau/nvkm/engine/disp/hdagt215.c
@@ -21,59 +21,31 @@
  *
  * Authors: Ben Skeggs
  */
-#include "nv50.h"
-#include "outp.h"
+#include "ior.h"
 
-#include <core/client.h>
-#include <subdev/timer.h>
-
-#include <nvif/cl5070.h>
-#include <nvif/unpack.h>
-
-int
-gt215_hda_eld(NV50_DISP_MTHD_V1)
+void
+gt215_hda_eld(struct nvkm_ior *ior, u8 *data, u8 size)
 {
-	struct nvkm_device *device = disp->base.engine.subdev.device;
-	union {
-		struct nv50_disp_sor_hda_eld_v0 v0;
-	} *args = data;
-	const u32 soff = outp->or * 0x800;
-	int ret = -ENOSYS, i;
-
-	nvif_ioctl(object, "disp sor hda eld size %d\n", size);
-	if (!(ret = nvif_unpack(ret, &data, &size, args->v0, 0, 0, true))) {
-		nvif_ioctl(object, "disp sor hda eld vers %d\n",
-			   args->v0.version);
-		if (size > 0x60)
-			return -E2BIG;
-	} else
-		return ret;
+	struct nvkm_device *device = ior->disp->engine.subdev.device;
+	const u32 soff = ior->id * 0x800;
+	int i;
 
-	if (size && args->v0.data[0]) {
-		if (outp->info.type == DCB_OUTPUT_DP) {
-			nvkm_mask(device, 0x61c1e0 + soff, 0x8000000d, 0x80000001);
-			nvkm_msec(device, 2000,
-				u32 tmp = nvkm_rd32(device, 0x61c1e0 + soff);
-				if (!(tmp & 0x80000000))
-					break;
-			);
-		}
-		for (i = 0; i < size; i++)
-			nvkm_wr32(device, 0x61c440 + soff, (i << 8) | args->v0.data[i]);
-		for (; i < 0x60; i++)
-			nvkm_wr32(device, 0x61c440 + soff, (i << 8));
-		nvkm_mask(device, 0x61c448 + soff, 0x80000003, 0x80000003);
-	} else {
-		if (outp->info.type == DCB_OUTPUT_DP) {
-			nvkm_mask(device, 0x61c1e0 + soff, 0x80000001, 0x80000000);
-			nvkm_msec(device, 2000,
-				u32 tmp = nvkm_rd32(device, 0x61c1e0 + soff);
-				if (!(tmp & 0x80000000))
-					break;
-			);
-		}
-		nvkm_mask(device, 0x61c448 + soff, 0x80000003, 0x80000000 | !!size);
-	}
+	for (i = 0; i < size; i++)
+		nvkm_wr32(device, 0x61c440 + soff, (i << 8) | data[i]);
+	for (; i < 0x60; i++)
+		nvkm_wr32(device, 0x61c440 + soff, (i << 8));
+	nvkm_mask(device, 0x61c448 + soff, 0x80000002, 0x80000002);
+}
 
-	return 0;
+void
+gt215_hda_hpd(struct nvkm_ior *ior, int head, bool present)
+{
+	struct nvkm_device *device = ior->disp->engine.subdev.device;
+	u32 data = 0x80000000;
+	u32 mask = 0x80000001;
+	if (present)
+		data |= 0x00000001;
+	else
+		mask |= 0x00000002;
+	nvkm_mask(device, 0x61c448 + ior->id * 0x800, mask, data);
 }
diff --git a/drivers/gpu/drm/nouveau/nvkm/engine/disp/hdmi.c b/drivers/gpu/drm/nouveau/nvkm/engine/disp/hdmi.c
new file mode 100644
index 000000000000..e82c68f18444
--- /dev/null
+++ b/drivers/gpu/drm/nouveau/nvkm/engine/disp/hdmi.c
@@ -0,0 +1,66 @@
+#include "hdmi.h"
+
+void pack_hdmi_infoframe(struct packed_hdmi_infoframe *packed_frame,
+			 u8 *raw_frame, ssize_t len)
+{
+	u32 header = 0;
+	u32 subpack0_low = 0;
+	u32 subpack0_high = 0;
+	u32 subpack1_low = 0;
+	u32 subpack1_high = 0;
+
+	switch (len) {
+		/*
+		 * "When in doubt, use brute force."
+		 *     -- Ken Thompson.
+		 */
+	default:
+		/*
+		 * We presume that no valid frame is longer than 17
+		 * octets, including header...  And truncate to that
+		 * if it's longer.
+		 */
+	case 17:
+		subpack1_high = (raw_frame[16] << 16);
+	case 16:
+		subpack1_high |= (raw_frame[15] << 8);
+	case 15:
+		subpack1_high |= raw_frame[14];
+	case 14:
+		subpack1_low = (raw_frame[13] << 24);
+	case 13:
+		subpack1_low |= (raw_frame[12] << 16);
+	case 12:
+		subpack1_low |= (raw_frame[11] << 8);
+	case 11:
+		subpack1_low |= raw_frame[10];
+	case 10:
+		subpack0_high = (raw_frame[9] << 16);
+	case 9:
+		subpack0_high |= (raw_frame[8] << 8);
+	case 8:
+		subpack0_high |= raw_frame[7];
+	case 7:
+		subpack0_low = (raw_frame[6] << 24);
+	case 6:
+		subpack0_low |= (raw_frame[5] << 16);
+	case 5:
+		subpack0_low |= (raw_frame[4] << 8);
+	case 4:
+		subpack0_low |= raw_frame[3];
+	case 3:
+		header = (raw_frame[2] << 16);
+	case 2:
+		header |= (raw_frame[1] << 8);
+	case 1:
+		header |= raw_frame[0];
+	case 0:
+		break;
+	}
+
+	packed_frame->header = header;
+	packed_frame->subpack0_low = subpack0_low;
+	packed_frame->subpack0_high = subpack0_high;
+	packed_frame->subpack1_low = subpack1_low;
+	packed_frame->subpack1_high = subpack1_high;
+}
diff --git a/drivers/gpu/drm/nouveau/nvkm/engine/disp/hdmi.h b/drivers/gpu/drm/nouveau/nvkm/engine/disp/hdmi.h
new file mode 100644
index 000000000000..528f5621a496
--- /dev/null
+++ b/drivers/gpu/drm/nouveau/nvkm/engine/disp/hdmi.h
@@ -0,0 +1,15 @@
+#ifndef __NVKM_DISP_HDMI_H__
+#define __NVKM_DISP_HDMI_H__
+#include "ior.h"
+
+struct packed_hdmi_infoframe {
+	u32 header;
+	u32 subpack0_low;
+	u32 subpack0_high;
+	u32 subpack1_low;
+	u32 subpack1_high;
+};
+
+void pack_hdmi_infoframe(struct packed_hdmi_infoframe *packed_frame,
+			 u8 *raw_frame, ssize_t len);
+#endif
diff --git a/drivers/gpu/drm/nouveau/nvkm/engine/disp/hdmig84.c b/drivers/gpu/drm/nouveau/nvkm/engine/disp/hdmig84.c
index 1c4256e8cbd6..661410f9b457 100644
--- a/drivers/gpu/drm/nouveau/nvkm/engine/disp/hdmig84.c
+++ b/drivers/gpu/drm/nouveau/nvkm/engine/disp/hdmig84.c
@@ -21,54 +21,42 @@
  *
  * Authors: Ben Skeggs
  */
-#include "nv50.h"
+#include "hdmi.h"
 
-#include <core/client.h>
-
-#include <nvif/cl5070.h>
-#include <nvif/unpack.h>
-
-int
-g84_hdmi_ctrl(NV50_DISP_MTHD_V1)
+void
+g84_hdmi_ctrl(struct nvkm_ior *ior, int head, bool enable, u8 max_ac_packet,
+	      u8 rekey, u8 *avi, u8 avi_size, u8 *vendor, u8 vendor_size)
 {
-	struct nvkm_device *device = disp->base.engine.subdev.device;
-	const u32 hoff = (head * 0x800);
-	union {
-		struct nv50_disp_sor_hdmi_pwr_v0 v0;
-	} *args = data;
-	u32 ctrl;
-	int ret = -ENOSYS;
+	struct nvkm_device *device = ior->disp->engine.subdev.device;
+	const u32 ctrl = 0x40000000 * enable |
+			 0x1f000000 /* ??? */ |
+			 max_ac_packet << 16 |
+			 rekey;
+	const u32 hoff = head * 0x800;
+	struct packed_hdmi_infoframe avi_infoframe;
+	struct packed_hdmi_infoframe vendor_infoframe;
 
-	nvif_ioctl(object, "disp sor hdmi ctrl size %d\n", size);
-	if (!(ret = nvif_unpack(ret, &data, &size, args->v0, 0, 0, false))) {
-		nvif_ioctl(object, "disp sor hdmi ctrl vers %d state %d "
-				   "max_ac_packet %d rekey %d\n",
-			   args->v0.version, args->v0.state,
-			   args->v0.max_ac_packet, args->v0.rekey);
-		if (args->v0.max_ac_packet > 0x1f || args->v0.rekey > 0x7f)
-			return -EINVAL;
-		ctrl  = 0x40000000 * !!args->v0.state;
-		ctrl |= args->v0.max_ac_packet << 16;
-		ctrl |= args->v0.rekey;
-		ctrl |= 0x1f000000; /* ??? */
-	} else
-		return ret;
+	pack_hdmi_infoframe(&avi_infoframe, avi, avi_size);
+	pack_hdmi_infoframe(&vendor_infoframe, vendor, vendor_size);
 
 	if (!(ctrl & 0x40000000)) {
 		nvkm_mask(device, 0x6165a4 + hoff, 0x40000000, 0x00000000);
+		nvkm_mask(device, 0x61653c + hoff, 0x00000001, 0x00000000);
 		nvkm_mask(device, 0x616520 + hoff, 0x00000001, 0x00000000);
 		nvkm_mask(device, 0x616500 + hoff, 0x00000001, 0x00000000);
-		return 0;
+		return;
 	}
 
 	/* AVI InfoFrame */
 	nvkm_mask(device, 0x616520 + hoff, 0x00000001, 0x00000000);
-	nvkm_wr32(device, 0x616528 + hoff, 0x000d0282);
-	nvkm_wr32(device, 0x61652c + hoff, 0x0000006f);
-	nvkm_wr32(device, 0x616530 + hoff, 0x00000000);
-	nvkm_wr32(device, 0x616534 + hoff, 0x00000000);
-	nvkm_wr32(device, 0x616538 + hoff, 0x00000000);
-	nvkm_mask(device, 0x616520 + hoff, 0x00000001, 0x00000001);
+	if (avi_size) {
+		nvkm_wr32(device, 0x616528 + hoff, avi_infoframe.header);
+		nvkm_wr32(device, 0x61652c + hoff, avi_infoframe.subpack0_low);
+		nvkm_wr32(device, 0x616530 + hoff, avi_infoframe.subpack0_high);
+		nvkm_wr32(device, 0x616534 + hoff, avi_infoframe.subpack1_low);
+		nvkm_wr32(device, 0x616538 + hoff, avi_infoframe.subpack1_high);
+		nvkm_mask(device, 0x616520 + hoff, 0x00000001, 0x00000001);
+	}
 
 	/* Audio InfoFrame */
 	nvkm_mask(device, 0x616500 + hoff, 0x00000001, 0x00000000);
@@ -77,6 +65,18 @@ g84_hdmi_ctrl(NV50_DISP_MTHD_V1)
 	nvkm_wr32(device, 0x616510 + hoff, 0x00000000);
 	nvkm_mask(device, 0x616500 + hoff, 0x00000001, 0x00000001);
 
+	/* Vendor InfoFrame */
+	nvkm_mask(device, 0x61653c + hoff, 0x00010001, 0x00010000);
+	if (vendor_size) {
+		nvkm_wr32(device, 0x616544 + hoff, vendor_infoframe.header);
+		nvkm_wr32(device, 0x616548 + hoff, vendor_infoframe.subpack0_low);
+		nvkm_wr32(device, 0x61654c + hoff, vendor_infoframe.subpack0_high);
+		/* Is there a second (or up to fourth?) set of subpack registers here? */
+		/* nvkm_wr32(device, 0x616550 + hoff, vendor_infoframe->subpack1_low); */
+		/* nvkm_wr32(device, 0x616554 + hoff, vendor_infoframe->subpack1_high); */
+		nvkm_mask(device, 0x61653c + hoff, 0x00010001, 0x00010001);
+	}
+
 	nvkm_mask(device, 0x6165d0 + hoff, 0x00070001, 0x00010001); /* SPARE, HW_CTS */
 	nvkm_mask(device, 0x616568 + hoff, 0x00010101, 0x00000000); /* ACR_CTRL, ?? */
 	nvkm_mask(device, 0x616578 + hoff, 0x80000000, 0x80000000); /* ACR_0441_ENABLE */
@@ -88,5 +88,4 @@ g84_hdmi_ctrl(NV50_DISP_MTHD_V1)
 
 	/* HDMI_CTRL */
 	nvkm_mask(device, 0x6165a4 + hoff, 0x5f1f007f, ctrl);
-	return 0;
 }
diff --git a/drivers/gpu/drm/nouveau/nvkm/engine/disp/hdmigf119.c b/drivers/gpu/drm/nouveau/nvkm/engine/disp/hdmigf119.c
index 632f02da1382..6cac0e72b4cc 100644
--- a/drivers/gpu/drm/nouveau/nvkm/engine/disp/hdmigf119.c
+++ b/drivers/gpu/drm/nouveau/nvkm/engine/disp/hdmigf119.c
@@ -21,53 +21,56 @@
  *
  * Authors: Ben Skeggs
  */
-#include "nv50.h"
+#include "hdmi.h"
 
-#include <core/client.h>
-
-#include <nvif/cl5070.h>
-#include <nvif/unpack.h>
-
-int
-gf119_hdmi_ctrl(NV50_DISP_MTHD_V1)
+void
+gf119_hdmi_ctrl(struct nvkm_ior *ior, int head, bool enable, u8 max_ac_packet,
+		u8 rekey, u8 *avi, u8 avi_size, u8 *vendor, u8 vendor_size)
 {
-	struct nvkm_device *device = disp->base.engine.subdev.device;
-	const u32 hoff = (head * 0x800);
-	union {
-		struct nv50_disp_sor_hdmi_pwr_v0 v0;
-	} *args = data;
-	u32 ctrl;
-	int ret = -ENOSYS;
+	struct nvkm_device *device = ior->disp->engine.subdev.device;
+	const u32 ctrl = 0x40000000 * enable |
+			 max_ac_packet << 16 |
+			 rekey;
+	const u32 hoff = head * 0x800;
+	struct packed_hdmi_infoframe avi_infoframe;
+	struct packed_hdmi_infoframe vendor_infoframe;
 
-	nvif_ioctl(object, "disp sor hdmi ctrl size %d\n", size);
-	if (!(ret = nvif_unpack(ret, &data, &size, args->v0, 0, 0, false))) {
-		nvif_ioctl(object, "disp sor hdmi ctrl vers %d state %d "
-				   "max_ac_packet %d rekey %d\n",
-			   args->v0.version, args->v0.state,
-			   args->v0.max_ac_packet, args->v0.rekey);
-		if (args->v0.max_ac_packet > 0x1f || args->v0.rekey > 0x7f)
-			return -EINVAL;
-		ctrl  = 0x40000000 * !!args->v0.state;
-		ctrl |= args->v0.max_ac_packet << 16;
-		ctrl |= args->v0.rekey;
-	} else
-		return ret;
+	pack_hdmi_infoframe(&avi_infoframe, avi, avi_size);
+	pack_hdmi_infoframe(&vendor_infoframe, vendor, vendor_size);
 
 	if (!(ctrl & 0x40000000)) {
 		nvkm_mask(device, 0x616798 + hoff, 0x40000000, 0x00000000);
+		nvkm_mask(device, 0x616730 + hoff, 0x00000001, 0x00000000);
 		nvkm_mask(device, 0x6167a4 + hoff, 0x00000001, 0x00000000);
 		nvkm_mask(device, 0x616714 + hoff, 0x00000001, 0x00000000);
-		return 0;
+		return;
 	}
 
 	/* AVI InfoFrame */
 	nvkm_mask(device, 0x616714 + hoff, 0x00000001, 0x00000000);
-	nvkm_wr32(device, 0x61671c + hoff, 0x000d0282);
-	nvkm_wr32(device, 0x616720 + hoff, 0x0000006f);
-	nvkm_wr32(device, 0x616724 + hoff, 0x00000000);
-	nvkm_wr32(device, 0x616728 + hoff, 0x00000000);
-	nvkm_wr32(device, 0x61672c + hoff, 0x00000000);
-	nvkm_mask(device, 0x616714 + hoff, 0x00000001, 0x00000001);
+	if (avi_size) {
+		nvkm_wr32(device, 0x61671c + hoff, avi_infoframe.header);
+		nvkm_wr32(device, 0x616720 + hoff, avi_infoframe.subpack0_low);
+		nvkm_wr32(device, 0x616724 + hoff, avi_infoframe.subpack0_high);
+		nvkm_wr32(device, 0x616728 + hoff, avi_infoframe.subpack1_low);
+		nvkm_wr32(device, 0x61672c + hoff, avi_infoframe.subpack1_high);
+		nvkm_mask(device, 0x616714 + hoff, 0x00000001, 0x00000001);
+	}
+
+	/* GENERIC(?) / Vendor InfoFrame? */
+	nvkm_mask(device, 0x616730 + hoff, 0x00010001, 0x00010000);
+	if (vendor_size) {
+		/*
+		 * These appear to be the audio infoframe registers,
+		 * but no other set of infoframe registers has yet
+		 * been found.
+		 */
+		nvkm_wr32(device, 0x616738 + hoff, vendor_infoframe.header);
+		nvkm_wr32(device, 0x61673c + hoff, vendor_infoframe.subpack0_low);
+		nvkm_wr32(device, 0x616740 + hoff, vendor_infoframe.subpack0_high);
+		/* Is there a second (or further?) set of subpack registers here? */
+		nvkm_mask(device, 0x616730 + hoff, 0x00000001, 0x00000001);
+	}
 
 	/* ??? InfoFrame? */
 	nvkm_mask(device, 0x6167a4 + hoff, 0x00000001, 0x00000000);
@@ -76,5 +79,4 @@ gf119_hdmi_ctrl(NV50_DISP_MTHD_V1)
 
 	/* HDMI_CTRL */
 	nvkm_mask(device, 0x616798 + hoff, 0x401f007f, ctrl);
-	return 0;
 }
diff --git a/drivers/gpu/drm/nouveau/nvkm/engine/disp/hdmigk104.c b/drivers/gpu/drm/nouveau/nvkm/engine/disp/hdmigk104.c
index 4e8067d511d7..ed0a6100d76b 100644
--- a/drivers/gpu/drm/nouveau/nvkm/engine/disp/hdmigk104.c
+++ b/drivers/gpu/drm/nouveau/nvkm/engine/disp/hdmigk104.c
@@ -21,54 +21,53 @@
  *
  * Authors: Ben Skeggs
  */
-#include "nv50.h"
+#include "hdmi.h"
 
-#include <core/client.h>
-
-#include <nvif/cl5070.h>
-#include <nvif/unpack.h>
-
-int
-gk104_hdmi_ctrl(NV50_DISP_MTHD_V1)
+void
+gk104_hdmi_ctrl(struct nvkm_ior *ior, int head, bool enable, u8 max_ac_packet,
+		u8 rekey, u8 *avi, u8 avi_size, u8 *vendor, u8 vendor_size)
 {
-	struct nvkm_device *device = disp->base.engine.subdev.device;
-	const u32 hoff = (head * 0x800);
-	const u32 hdmi = (head * 0x400);
-	union {
-		struct nv50_disp_sor_hdmi_pwr_v0 v0;
-	} *args = data;
-	u32 ctrl;
-	int ret = -ENOSYS;
+	struct nvkm_device *device = ior->disp->engine.subdev.device;
+	const u32 ctrl = 0x40000000 * enable |
+			 max_ac_packet << 16 |
+			 rekey;
+	const u32 hoff = head * 0x800;
+	const u32 hdmi = head * 0x400;
+	struct packed_hdmi_infoframe avi_infoframe;
+	struct packed_hdmi_infoframe vendor_infoframe;
 
-	nvif_ioctl(object, "disp sor hdmi ctrl size %d\n", size);
-	if (!(ret = nvif_unpack(ret, &data, &size, args->v0, 0, 0, false))) {
-		nvif_ioctl(object, "disp sor hdmi ctrl vers %d state %d "
-				   "max_ac_packet %d rekey %d\n",
-			   args->v0.version, args->v0.state,
-			   args->v0.max_ac_packet, args->v0.rekey);
-		if (args->v0.max_ac_packet > 0x1f || args->v0.rekey > 0x7f)
-			return -EINVAL;
-		ctrl  = 0x40000000 * !!args->v0.state;
-		ctrl |= args->v0.max_ac_packet << 16;
-		ctrl |= args->v0.rekey;
-	} else
-		return ret;
+	pack_hdmi_infoframe(&avi_infoframe, avi, avi_size);
+	pack_hdmi_infoframe(&vendor_infoframe, vendor, vendor_size);
 
 	if (!(ctrl & 0x40000000)) {
 		nvkm_mask(device, 0x616798 + hoff, 0x40000000, 0x00000000);
+		nvkm_mask(device, 0x690100 + hdmi, 0x00000001, 0x00000000);
 		nvkm_mask(device, 0x6900c0 + hdmi, 0x00000001, 0x00000000);
 		nvkm_mask(device, 0x690000 + hdmi, 0x00000001, 0x00000000);
-		return 0;
+		return;
 	}
 
 	/* AVI InfoFrame */
 	nvkm_mask(device, 0x690000 + hdmi, 0x00000001, 0x00000000);
-	nvkm_wr32(device, 0x690008 + hdmi, 0x000d0282);
-	nvkm_wr32(device, 0x69000c + hdmi, 0x0000006f);
-	nvkm_wr32(device, 0x690010 + hdmi, 0x00000000);
-	nvkm_wr32(device, 0x690014 + hdmi, 0x00000000);
-	nvkm_wr32(device, 0x690018 + hdmi, 0x00000000);
-	nvkm_mask(device, 0x690000 + hdmi, 0x00000001, 0x00000001);
+	if (avi_size) {
+		nvkm_wr32(device, 0x690008 + hdmi, avi_infoframe.header);
+		nvkm_wr32(device, 0x69000c + hdmi, avi_infoframe.subpack0_low);
+		nvkm_wr32(device, 0x690010 + hdmi, avi_infoframe.subpack0_high);
+		nvkm_wr32(device, 0x690014 + hdmi, avi_infoframe.subpack1_low);
+		nvkm_wr32(device, 0x690018 + hdmi, avi_infoframe.subpack1_high);
+		nvkm_mask(device, 0x690000 + hdmi, 0x00000001, 0x00000001);
+	}
+
+	/* GENERIC(?) / Vendor InfoFrame? */
+	nvkm_mask(device, 0x690100 + hdmi, 0x00010001, 0x00000000);
+	if (vendor_size) {
+		nvkm_wr32(device, 0x690108 + hdmi, vendor_infoframe.header);
+		nvkm_wr32(device, 0x69010c + hdmi, vendor_infoframe.subpack0_low);
+		nvkm_wr32(device, 0x690110 + hdmi, vendor_infoframe.subpack0_high);
+		/* Is there a second (or further?) set of subpack registers here? */
+		nvkm_mask(device, 0x690100 + hdmi, 0x00000001, 0x00000001);
+	}
+
 
 	/* ??? InfoFrame? */
 	nvkm_mask(device, 0x6900c0 + hdmi, 0x00000001, 0x00000000);
@@ -80,5 +79,4 @@ gk104_hdmi_ctrl(NV50_DISP_MTHD_V1)
 
 	/* HDMI_CTRL */
 	nvkm_mask(device, 0x616798 + hoff, 0x401f007f, ctrl);
-	return 0;
 }
diff --git a/drivers/gpu/drm/nouveau/nvkm/engine/disp/hdmigt215.c b/drivers/gpu/drm/nouveau/nvkm/engine/disp/hdmigt215.c
index f1afc16494b6..0993d223bb9c 100644
--- a/drivers/gpu/drm/nouveau/nvkm/engine/disp/hdmigt215.c
+++ b/drivers/gpu/drm/nouveau/nvkm/engine/disp/hdmigt215.c
@@ -21,55 +21,42 @@
  *
  * Authors: Ben Skeggs
  */
-#include "nv50.h"
-#include "outp.h"
+#include "hdmi.h"
 
-#include <core/client.h>
-
-#include <nvif/cl5070.h>
-#include <nvif/unpack.h>
-
-int
-gt215_hdmi_ctrl(NV50_DISP_MTHD_V1)
+void
+gt215_hdmi_ctrl(struct nvkm_ior *ior, int head, bool enable, u8 max_ac_packet,
+		u8 rekey, u8 *avi, u8 avi_size, u8 *vendor, u8 vendor_size)
 {
-	struct nvkm_device *device = disp->base.engine.subdev.device;
-	const u32 soff = outp->or * 0x800;
-	union {
-		struct nv50_disp_sor_hdmi_pwr_v0 v0;
-	} *args = data;
-	u32 ctrl;
-	int ret = -ENOSYS;
+	struct nvkm_device *device = ior->disp->engine.subdev.device;
+	const u32 ctrl = 0x40000000 * enable |
+			 0x1f000000 /* ??? */ |
+			 max_ac_packet << 16 |
+			 rekey;
+	const u32 soff = nv50_ior_base(ior);
+	struct packed_hdmi_infoframe avi_infoframe;
+	struct packed_hdmi_infoframe vendor_infoframe;
 
-	nvif_ioctl(object, "disp sor hdmi ctrl size %d\n", size);
-	if (!(ret = nvif_unpack(ret, &data, &size, args->v0, 0, 0, false))) {
-		nvif_ioctl(object, "disp sor hdmi ctrl vers %d state %d "
-				   "max_ac_packet %d rekey %d\n",
-			   args->v0.version, args->v0.state,
-			   args->v0.max_ac_packet, args->v0.rekey);
-		if (args->v0.max_ac_packet > 0x1f || args->v0.rekey > 0x7f)
-			return -EINVAL;
-		ctrl  = 0x40000000 * !!args->v0.state;
-		ctrl |= args->v0.max_ac_packet << 16;
-		ctrl |= args->v0.rekey;
-		ctrl |= 0x1f000000; /* ??? */
-	} else
-		return ret;
+	pack_hdmi_infoframe(&avi_infoframe, avi, avi_size);
+	pack_hdmi_infoframe(&vendor_infoframe, vendor, vendor_size);
 
 	if (!(ctrl & 0x40000000)) {
 		nvkm_mask(device, 0x61c5a4 + soff, 0x40000000, 0x00000000);
+		nvkm_mask(device, 0x61c53c + soff, 0x00000001, 0x00000000);
 		nvkm_mask(device, 0x61c520 + soff, 0x00000001, 0x00000000);
 		nvkm_mask(device, 0x61c500 + soff, 0x00000001, 0x00000000);
-		return 0;
+		return;
 	}
 
 	/* AVI InfoFrame */
 	nvkm_mask(device, 0x61c520 + soff, 0x00000001, 0x00000000);
-	nvkm_wr32(device, 0x61c528 + soff, 0x000d0282);
-	nvkm_wr32(device, 0x61c52c + soff, 0x0000006f);
-	nvkm_wr32(device, 0x61c530 + soff, 0x00000000);
-	nvkm_wr32(device, 0x61c534 + soff, 0x00000000);
-	nvkm_wr32(device, 0x61c538 + soff, 0x00000000);
-	nvkm_mask(device, 0x61c520 + soff, 0x00000001, 0x00000001);
+	if (avi_size) {
+		nvkm_wr32(device, 0x61c528 + soff, avi_infoframe.header);
+		nvkm_wr32(device, 0x61c52c + soff, avi_infoframe.subpack0_low);
+		nvkm_wr32(device, 0x61c530 + soff, avi_infoframe.subpack0_high);
+		nvkm_wr32(device, 0x61c534 + soff, avi_infoframe.subpack1_low);
+		nvkm_wr32(device, 0x61c538 + soff, avi_infoframe.subpack1_high);
+		nvkm_mask(device, 0x61c520 + soff, 0x00000001, 0x00000001);
+	}
 
 	/* Audio InfoFrame */
 	nvkm_mask(device, 0x61c500 + soff, 0x00000001, 0x00000000);
@@ -78,6 +65,18 @@ gt215_hdmi_ctrl(NV50_DISP_MTHD_V1)
 	nvkm_wr32(device, 0x61c510 + soff, 0x00000000);
 	nvkm_mask(device, 0x61c500 + soff, 0x00000001, 0x00000001);
 
+	/* Vendor InfoFrame */
+	nvkm_mask(device, 0x61c53c + soff, 0x00010001, 0x00010000);
+	if (vendor_size) {
+		nvkm_wr32(device, 0x61c544 + soff, vendor_infoframe.header);
+		nvkm_wr32(device, 0x61c548 + soff, vendor_infoframe.subpack0_low);
+		nvkm_wr32(device, 0x61c54c + soff, vendor_infoframe.subpack0_high);
+		/* Is there a second (or up to fourth?) set of subpack registers here? */
+		/* nvkm_wr32(device, 0x61c550 + soff, vendor_infoframe.subpack1_low); */
+		/* nvkm_wr32(device, 0x61c554 + soff, vendor_infoframe.subpack1_high); */
+		nvkm_mask(device, 0x61c53c + soff, 0x00010001, 0x00010001);
+	}
+
 	nvkm_mask(device, 0x61c5d0 + soff, 0x00070001, 0x00010001); /* SPARE, HW_CTS */
 	nvkm_mask(device, 0x61c568 + soff, 0x00010101, 0x00000000); /* ACR_CTRL, ?? */
 	nvkm_mask(device, 0x61c578 + soff, 0x80000000, 0x80000000); /* ACR_0441_ENABLE */
@@ -89,5 +88,4 @@ gt215_hdmi_ctrl(NV50_DISP_MTHD_V1)
 
 	/* HDMI_CTRL */
 	nvkm_mask(device, 0x61c5a4 + soff, 0x5f1f007f, ctrl);
-	return 0;
 }
diff --git a/drivers/gpu/drm/nouveau/nvkm/engine/disp/head.c b/drivers/gpu/drm/nouveau/nvkm/engine/disp/head.c
new file mode 100644
index 000000000000..5c557f3e6c2c
--- /dev/null
+++ b/drivers/gpu/drm/nouveau/nvkm/engine/disp/head.c
@@ -0,0 +1,105 @@
+/*
+ * Copyright 2017 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 <bskeggs@redhat.com>
+ */
+#include "head.h"
+
+#include <core/client.h>
+
+#include <nvif/cl0046.h>
+#include <nvif/unpack.h>
+
+struct nvkm_head *
+nvkm_head_find(struct nvkm_disp *disp, int id)
+{
+	struct nvkm_head *head;
+	list_for_each_entry(head, &disp->head, head) {
+		if (head->id == id)
+			return head;
+	}
+	return NULL;
+}
+
+int
+nvkm_head_mthd_scanoutpos(struct nvkm_object *object,
+			  struct nvkm_head *head, void *data, u32 size)
+{
+	union {
+		struct nv04_disp_scanoutpos_v0 v0;
+	} *args = data;
+	int ret = -ENOSYS;
+
+	nvif_ioctl(object, "head scanoutpos size %d\n", size);
+	if (!(ret = nvif_unpack(ret, &data, &size, args->v0, 0, 0, false))) {
+		nvif_ioctl(object, "head scanoutpos vers %d\n",
+			   args->v0.version);
+
+		head->func->state(head, &head->arm);
+		args->v0.vtotal  = head->arm.vtotal;
+		args->v0.vblanks = head->arm.vblanks;
+		args->v0.vblanke = head->arm.vblanke;
+		args->v0.htotal  = head->arm.htotal;
+		args->v0.hblanks = head->arm.hblanks;
+		args->v0.hblanke = head->arm.hblanke;
+
+		/* We don't support reading htotal/vtotal on pre-NV50 VGA,
+		 * so we have to give up and trigger the timestamping
+		 * fallback in the drm core.
+		 */
+		if (!args->v0.vtotal || !args->v0.htotal)
+			return -ENOTSUPP;
+
+		args->v0.time[0] = ktime_to_ns(ktime_get());
+		head->func->rgpos(head, &args->v0.hline, &args->v0.vline);
+		args->v0.time[1] = ktime_to_ns(ktime_get());
+	} else
+		return ret;
+
+	return 0;
+}
+
+void
+nvkm_head_del(struct nvkm_head **phead)
+{
+	struct nvkm_head *head = *phead;
+	if (head) {
+		HEAD_DBG(head, "dtor");
+		list_del(&head->head);
+		kfree(*phead);
+		*phead = NULL;
+	}
+}
+
+int
+nvkm_head_new_(const struct nvkm_head_func *func,
+	       struct nvkm_disp *disp, int id)
+{
+	struct nvkm_head *head;
+	if (!(head = kzalloc(sizeof(*head), GFP_KERNEL)))
+		return -ENOMEM;
+	head->func = func;
+	head->disp = disp;
+	head->id = id;
+	list_add_tail(&head->head, &disp->head);
+	HEAD_DBG(head, "ctor");
+	return 0;
+}
diff --git a/drivers/gpu/drm/nouveau/nvkm/engine/disp/head.h b/drivers/gpu/drm/nouveau/nvkm/engine/disp/head.h
new file mode 100644
index 000000000000..b04c49d2eeeb
--- /dev/null
+++ b/drivers/gpu/drm/nouveau/nvkm/engine/disp/head.h
@@ -0,0 +1,56 @@
+#ifndef __NVKM_DISP_HEAD_H__
+#define __NVKM_DISP_HEAD_H__
+#include "priv.h"
+
+struct nvkm_head {
+	const struct nvkm_head_func *func;
+	struct nvkm_disp *disp;
+	int id;
+
+	struct list_head head;
+
+	struct nvkm_head_state {
+		u16 htotal;
+		u16 hsynce;
+		u16 hblanke;
+		u16 hblanks;
+		u16 vtotal;
+		u16 vsynce;
+		u16 vblanke;
+		u16 vblanks;
+		u32 hz;
+
+		/* Prior to GF119, these are set by the OR. */
+		struct {
+			u8 depth;
+		} or;
+	} arm, asy;
+};
+
+int nvkm_head_new_(const struct nvkm_head_func *, struct nvkm_disp *, int id);
+void nvkm_head_del(struct nvkm_head **);
+int nvkm_head_mthd_scanoutpos(struct nvkm_object *,
+			      struct nvkm_head *, void *, u32);
+struct nvkm_head *nvkm_head_find(struct nvkm_disp *, int id);
+
+struct nvkm_head_func {
+	void (*state)(struct nvkm_head *, struct nvkm_head_state *);
+	void (*rgpos)(struct nvkm_head *, u16 *hline, u16 *vline);
+	void (*rgclk)(struct nvkm_head *, int div);
+	void (*vblank_get)(struct nvkm_head *);
+	void (*vblank_put)(struct nvkm_head *);
+};
+
+void nv50_head_rgpos(struct nvkm_head *, u16 *, u16 *);
+
+#define HEAD_MSG(h,l,f,a...) do {                                              \
+	struct nvkm_head *_h = (h);                                            \
+	nvkm_##l(&_h->disp->engine.subdev, "head-%d: "f"\n", _h->id, ##a);     \
+} while(0)
+#define HEAD_WARN(h,f,a...) HEAD_MSG((h), warn, f, ##a)
+#define HEAD_DBG(h,f,a...) HEAD_MSG((h), debug, f, ##a)
+
+int nv04_head_new(struct nvkm_disp *, int id);
+int nv50_head_new(struct nvkm_disp *, int id);
+int gf119_head_new(struct nvkm_disp *, int id);
+#endif
diff --git a/drivers/gpu/drm/nouveau/nvkm/engine/disp/headgf119.c b/drivers/gpu/drm/nouveau/nvkm/engine/disp/headgf119.c
new file mode 100644
index 000000000000..b33552757647
--- /dev/null
+++ b/drivers/gpu/drm/nouveau/nvkm/engine/disp/headgf119.c
@@ -0,0 +1,96 @@
+/*
+ * Copyright 2017 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 <bskeggs@redhat.com>
+ */
+#include "head.h"
+
+static void
+gf119_head_vblank_put(struct nvkm_head *head)
+{
+	struct nvkm_device *device = head->disp->engine.subdev.device;
+	const u32 hoff = head->id * 0x800;
+	nvkm_mask(device, 0x6100c0 + hoff, 0x00000001, 0x00000000);
+}
+
+static void
+gf119_head_vblank_get(struct nvkm_head *head)
+{
+	struct nvkm_device *device = head->disp->engine.subdev.device;
+	const u32 hoff = head->id * 0x800;
+	nvkm_mask(device, 0x6100c0 + hoff, 0x00000001, 0x00000001);
+}
+
+static void
+gf119_head_rgclk(struct nvkm_head *head, int div)
+{
+	struct nvkm_device *device = head->disp->engine.subdev.device;
+	nvkm_mask(device, 0x612200 + (head->id * 0x800), 0x0000000f, div);
+}
+
+static void
+gf119_head_state(struct nvkm_head *head, struct nvkm_head_state *state)
+{
+	struct nvkm_device *device = head->disp->engine.subdev.device;
+	const u32 hoff = (state == &head->asy) * 0x20000 + head->id * 0x300;
+	u32 data;
+
+	data = nvkm_rd32(device, 0x640414 + hoff);
+	state->vtotal = (data & 0xffff0000) >> 16;
+	state->htotal = (data & 0x0000ffff);
+	data = nvkm_rd32(device, 0x640418 + hoff);
+	state->vsynce = (data & 0xffff0000) >> 16;
+	state->hsynce = (data & 0x0000ffff);
+	data = nvkm_rd32(device, 0x64041c + hoff);
+	state->vblanke = (data & 0xffff0000) >> 16;
+	state->hblanke = (data & 0x0000ffff);
+	data = nvkm_rd32(device, 0x640420 + hoff);
+	state->vblanks = (data & 0xffff0000) >> 16;
+	state->hblanks = (data & 0x0000ffff);
+	state->hz = nvkm_rd32(device, 0x640450 + hoff);
+
+	data = nvkm_rd32(device, 0x640404 + hoff);
+	switch ((data & 0x000003c0) >> 6) {
+	case 6: state->or.depth = 30; break;
+	case 5: state->or.depth = 24; break;
+	case 2: state->or.depth = 18; break;
+	case 0: state->or.depth = 18; break; /*XXX: "default" */
+	default:
+		state->or.depth = 18;
+		WARN_ON(1);
+		break;
+	}
+}
+
+static const struct nvkm_head_func
+gf119_head = {
+	.state = gf119_head_state,
+	.rgpos = nv50_head_rgpos,
+	.rgclk = gf119_head_rgclk,
+	.vblank_get = gf119_head_vblank_get,
+	.vblank_put = gf119_head_vblank_put,
+};
+
+int
+gf119_head_new(struct nvkm_disp *disp, int id)
+{
+	return nvkm_head_new_(&gf119_head, disp, id);
+}
diff --git a/drivers/gpu/drm/nouveau/nvkm/engine/disp/headnv04.c b/drivers/gpu/drm/nouveau/nvkm/engine/disp/headnv04.c
new file mode 100644
index 000000000000..dcf459282aa1
--- /dev/null
+++ b/drivers/gpu/drm/nouveau/nvkm/engine/disp/headnv04.c
@@ -0,0 +1,74 @@
+/*
+ * Copyright 2017 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 <bskeggs@redhat.com>
+ */
+#include "head.h"
+
+static void
+nv04_head_vblank_put(struct nvkm_head *head)
+{
+	struct nvkm_device *device = head->disp->engine.subdev.device;
+	nvkm_wr32(device, 0x600140 + (head->id * 0x2000) , 0x00000000);
+}
+
+static void
+nv04_head_vblank_get(struct nvkm_head *head)
+{
+	struct nvkm_device *device = head->disp->engine.subdev.device;
+	nvkm_wr32(device, 0x600140 + (head->id * 0x2000) , 0x00000001);
+}
+
+static void
+nv04_head_rgpos(struct nvkm_head *head, u16 *hline, u16 *vline)
+{
+	struct nvkm_device *device = head->disp->engine.subdev.device;
+	u32 data = nvkm_rd32(device, 0x600868 + (head->id * 0x2000));
+	*hline = (data & 0xffff0000) >> 16;
+	*vline = (data & 0x0000ffff);
+}
+
+static void
+nv04_head_state(struct nvkm_head *head, struct nvkm_head_state *state)
+{
+	struct nvkm_device *device = head->disp->engine.subdev.device;
+	const u32 hoff = head->id * 0x0200;
+	state->vblanks = nvkm_rd32(device, 0x680800 + hoff) & 0x0000ffff;
+	state->vtotal  = nvkm_rd32(device, 0x680804 + hoff) & 0x0000ffff;
+	state->vblanke = state->vtotal - 1;
+	state->hblanks = nvkm_rd32(device, 0x680820 + hoff) & 0x0000ffff;
+	state->htotal  = nvkm_rd32(device, 0x680824 + hoff) & 0x0000ffff;
+	state->hblanke = state->htotal - 1;
+}
+
+static const struct nvkm_head_func
+nv04_head = {
+	.state = nv04_head_state,
+	.rgpos = nv04_head_rgpos,
+	.vblank_get = nv04_head_vblank_get,
+	.vblank_put = nv04_head_vblank_put,
+};
+
+int
+nv04_head_new(struct nvkm_disp *disp, int id)
+{
+	return nvkm_head_new_(&nv04_head, disp, id);
+}
diff --git a/drivers/gpu/drm/nouveau/nvkm/engine/disp/headnv50.c b/drivers/gpu/drm/nouveau/nvkm/engine/disp/headnv50.c
new file mode 100644
index 000000000000..c80d06d5168f
--- /dev/null
+++ b/drivers/gpu/drm/nouveau/nvkm/engine/disp/headnv50.c
@@ -0,0 +1,92 @@
+/*
+ * Copyright 2017 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 <bskeggs@redhat.com>
+ */
+#include "head.h"
+
+static void
+nv50_head_vblank_put(struct nvkm_head *head)
+{
+	struct nvkm_device *device = head->disp->engine.subdev.device;
+	nvkm_mask(device, 0x61002c, (4 << head->id), 0);
+}
+
+static void
+nv50_head_vblank_get(struct nvkm_head *head)
+{
+	struct nvkm_device *device = head->disp->engine.subdev.device;
+	nvkm_mask(device, 0x61002c, (4 << head->id), (4 << head->id));
+}
+
+static void
+nv50_head_rgclk(struct nvkm_head *head, int div)
+{
+	struct nvkm_device *device = head->disp->engine.subdev.device;
+	nvkm_mask(device, 0x614200 + (head->id * 0x800), 0x0000000f, div);
+}
+
+void
+nv50_head_rgpos(struct nvkm_head *head, u16 *hline, u16 *vline)
+{
+	struct nvkm_device *device = head->disp->engine.subdev.device;
+	const u32 hoff = head->id * 0x800;
+	/* vline read locks hline. */
+	*vline = nvkm_rd32(device, 0x616340 + hoff) & 0x0000ffff;
+	*hline = nvkm_rd32(device, 0x616344 + hoff) & 0x0000ffff;
+}
+
+static void
+nv50_head_state(struct nvkm_head *head, struct nvkm_head_state *state)
+{
+	struct nvkm_device *device = head->disp->engine.subdev.device;
+	const u32 hoff = head->id * 0x540 + (state == &head->arm) * 4;
+	u32 data;
+
+	data = nvkm_rd32(device, 0x610ae8 + hoff);
+	state->vblanke = (data & 0xffff0000) >> 16;
+	state->hblanke = (data & 0x0000ffff);
+	data = nvkm_rd32(device, 0x610af0 + hoff);
+	state->vblanks = (data & 0xffff0000) >> 16;
+	state->hblanks = (data & 0x0000ffff);
+	data = nvkm_rd32(device, 0x610af8 + hoff);
+	state->vtotal = (data & 0xffff0000) >> 16;
+	state->htotal = (data & 0x0000ffff);
+	data = nvkm_rd32(device, 0x610b00 + hoff);
+	state->vsynce = (data & 0xffff0000) >> 16;
+	state->hsynce = (data & 0x0000ffff);
+	state->hz = (nvkm_rd32(device, 0x610ad0 + hoff) & 0x003fffff) * 1000;
+}
+
+static const struct nvkm_head_func
+nv50_head = {
+	.state = nv50_head_state,
+	.rgpos = nv50_head_rgpos,
+	.rgclk = nv50_head_rgclk,
+	.vblank_get = nv50_head_vblank_get,
+	.vblank_put = nv50_head_vblank_put,
+};
+
+int
+nv50_head_new(struct nvkm_disp *disp, int id)
+{
+	return nvkm_head_new_(&nv50_head, disp, id);
+}
diff --git a/drivers/gpu/drm/nouveau/nvkm/engine/disp/ior.c b/drivers/gpu/drm/nouveau/nvkm/engine/disp/ior.c
new file mode 100644
index 000000000000..a475ea56795c
--- /dev/null
+++ b/drivers/gpu/drm/nouveau/nvkm/engine/disp/ior.c
@@ -0,0 +1,72 @@
+/*
+ * Copyright 2017 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 <bskeggs@redhat.com>
+ */
+#include "ior.h"
+
+static const char *
+nvkm_ior_name[] = {
+	[DAC] = "DAC",
+	[SOR] = "SOR",
+	[PIOR] = "PIOR",
+};
+
+struct nvkm_ior *
+nvkm_ior_find(struct nvkm_disp *disp, enum nvkm_ior_type type, int id)
+{
+	struct nvkm_ior *ior;
+	list_for_each_entry(ior, &disp->ior, head) {
+		if (ior->type == type && (id < 0 || ior->id == id))
+			return ior;
+	}
+	return NULL;
+}
+
+void
+nvkm_ior_del(struct nvkm_ior **pior)
+{
+	struct nvkm_ior *ior = *pior;
+	if (ior) {
+		IOR_DBG(ior, "dtor");
+		list_del(&ior->head);
+		kfree(*pior);
+		*pior = NULL;
+	}
+}
+
+int
+nvkm_ior_new_(const struct nvkm_ior_func *func, struct nvkm_disp *disp,
+	      enum nvkm_ior_type type, int id)
+{
+	struct nvkm_ior *ior;
+	if (!(ior = kzalloc(sizeof(*ior), GFP_KERNEL)))
+		return -ENOMEM;
+	ior->func = func;
+	ior->disp = disp;
+	ior->type = type;
+	ior->id = id;
+	snprintf(ior->name, sizeof(ior->name), "%s-%d",
+		 nvkm_ior_name[ior->type], ior->id);
+	list_add_tail(&ior->head, &disp->ior);
+	IOR_DBG(ior, "ctor");
+	return 0;
+}
diff --git a/drivers/gpu/drm/nouveau/nvkm/engine/disp/ior.h b/drivers/gpu/drm/nouveau/nvkm/engine/disp/ior.h
new file mode 100644
index 000000000000..a24312fb0228
--- /dev/null
+++ b/drivers/gpu/drm/nouveau/nvkm/engine/disp/ior.h
@@ -0,0 +1,169 @@
+#ifndef __NVKM_DISP_IOR_H__
+#define __NVKM_DISP_IOR_H__
+#include "priv.h"
+struct nvkm_i2c_aux;
+
+struct nvkm_ior {
+	const struct nvkm_ior_func *func;
+	struct nvkm_disp *disp;
+	enum nvkm_ior_type {
+		DAC,
+		SOR,
+		PIOR,
+	} type;
+	int id;
+	char name[8];
+
+	struct list_head head;
+
+	struct nvkm_ior_state {
+		struct nvkm_outp *outp;
+		unsigned rgdiv;
+		unsigned proto_evo:4;
+		enum nvkm_ior_proto {
+			CRT,
+			TMDS,
+			LVDS,
+			DP,
+			UNKNOWN
+		} proto:3;
+		unsigned link:2;
+		unsigned head:4;
+	} arm, asy;
+
+	/* Armed DP state. */
+	struct {
+		bool mst;
+		bool ef;
+		u8 nr;
+		u8 bw;
+	} dp;
+};
+
+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 (*power)(struct nvkm_ior *, bool normal, bool pu,
+		      bool data, bool vsync, bool hsync);
+	int (*sense)(struct nvkm_ior *, u32 loadval);
+	void (*clock)(struct nvkm_ior *);
+	void (*war_2)(struct nvkm_ior *);
+	void (*war_3)(struct nvkm_ior *);
+
+	struct {
+		void (*ctrl)(struct nvkm_ior *, int head, bool enable,
+			     u8 max_ac_packet, u8 rekey, u8 *avi, u8 avi_size,
+			     u8 *vendor, u8 vendor_size);
+	} hdmi;
+
+	struct {
+		u8 lanes[4];
+		int (*links)(struct nvkm_ior *, struct nvkm_i2c_aux *);
+		void (*power)(struct nvkm_ior *, int nr);
+		void (*pattern)(struct nvkm_ior *, int pattern);
+		void (*drive)(struct nvkm_ior *, int ln, int pc,
+			      int dc, int pe, int tx_pu);
+		void (*vcpi)(struct nvkm_ior *, int head, u8 slot,
+			     u8 slot_nr, u16 pbn, u16 aligned);
+		void (*audio)(struct nvkm_ior *, int head, bool enable);
+		void (*audio_sym)(struct nvkm_ior *, int head, u16 h, u32 v);
+		void (*activesym)(struct nvkm_ior *, int head,
+				  u8 TU, u8 VTUa, u8 VTUf, u8 VTUi);
+		void (*watermark)(struct nvkm_ior *, int head, u8 watermark);
+	} dp;
+
+	struct {
+		void (*hpd)(struct nvkm_ior *, int head, bool present);
+		void (*eld)(struct nvkm_ior *, u8 *data, u8 size);
+	} hda;
+};
+
+int nvkm_ior_new_(const struct nvkm_ior_func *func, struct nvkm_disp *,
+		  enum nvkm_ior_type type, int id);
+void nvkm_ior_del(struct nvkm_ior **);
+struct nvkm_ior *nvkm_ior_find(struct nvkm_disp *, enum nvkm_ior_type, int id);
+
+static inline u32
+nv50_ior_base(struct nvkm_ior *ior)
+{
+	return ior->id * 0x800;
+}
+
+void nv50_dac_power(struct nvkm_ior *, bool, bool, bool, bool, bool);
+int nv50_dac_sense(struct nvkm_ior *, u32);
+
+void nv50_pior_depth(struct nvkm_ior *, struct nvkm_ior_state *, u32 ctrl);
+
+static inline u32
+nv50_sor_link(struct nvkm_ior *ior)
+{
+	return nv50_ior_base(ior) + ((ior->asy.link == 2) * 0x80);
+}
+
+int nv50_sor_new_(const struct nvkm_ior_func *, struct nvkm_disp *, int id);
+void nv50_sor_state(struct nvkm_ior *, struct nvkm_ior_state *);
+void nv50_sor_power(struct nvkm_ior *, bool, bool, bool, bool, bool);
+void nv50_sor_clock(struct nvkm_ior *);
+
+void g94_sor_state(struct nvkm_ior *, struct nvkm_ior_state *);
+int g94_sor_dp_links(struct nvkm_ior *, struct nvkm_i2c_aux *);
+void g94_sor_dp_power(struct nvkm_ior *, int);
+void g94_sor_dp_pattern(struct nvkm_ior *, int);
+void g94_sor_dp_drive(struct nvkm_ior *, int, int, int, int, int);
+void g94_sor_dp_audio_sym(struct nvkm_ior *, int, u16, u32);
+void g94_sor_dp_activesym(struct nvkm_ior *, int, u8, u8, u8, u8);
+void g94_sor_dp_watermark(struct nvkm_ior *, int, u8);
+
+void gt215_sor_dp_audio(struct nvkm_ior *, int, bool);
+
+int gf119_sor_new_(const struct nvkm_ior_func *, struct nvkm_disp *, int id);
+void gf119_sor_state(struct nvkm_ior *, struct nvkm_ior_state *);
+void gf119_sor_clock(struct nvkm_ior *);
+int gf119_sor_dp_links(struct nvkm_ior *, struct nvkm_i2c_aux *);
+void gf119_sor_dp_pattern(struct nvkm_ior *, int);
+void gf119_sor_dp_drive(struct nvkm_ior *, int, int, int, int, int);
+void gf119_sor_dp_vcpi(struct nvkm_ior *, int, u8, u8, u16, u16);
+void gf119_sor_dp_audio(struct nvkm_ior *, int, bool);
+void gf119_sor_dp_audio_sym(struct nvkm_ior *, int, u16, u32);
+void gf119_sor_dp_watermark(struct nvkm_ior *, int, u8);
+
+void gm107_sor_dp_pattern(struct nvkm_ior *, int);
+
+void g84_hdmi_ctrl(struct nvkm_ior *, int, bool, u8, u8, u8 *, u8 , u8 *, u8);
+void gt215_hdmi_ctrl(struct nvkm_ior *, int, bool, u8, u8, u8 *, u8 , u8 *, u8);
+void gf119_hdmi_ctrl(struct nvkm_ior *, int, bool, u8, u8, u8 *, u8 , u8 *, u8);
+void gk104_hdmi_ctrl(struct nvkm_ior *, int, bool, u8, u8, u8 *, u8 , u8 *, u8);
+
+void gt215_hda_hpd(struct nvkm_ior *, int, bool);
+void gt215_hda_eld(struct nvkm_ior *, u8 *, u8);
+
+void gf119_hda_hpd(struct nvkm_ior *, int, bool);
+void gf119_hda_eld(struct nvkm_ior *, u8 *, u8);
+
+#define IOR_MSG(i,l,f,a...) do {                                               \
+	struct nvkm_ior *_ior = (i);                                           \
+	nvkm_##l(&_ior->disp->engine.subdev, "%s: "f, _ior->name, ##a);        \
+} while(0)
+#define IOR_WARN(i,f,a...) IOR_MSG((i), warn, f, ##a)
+#define IOR_DBG(i,f,a...) IOR_MSG((i), debug, f, ##a)
+
+int nv50_dac_new(struct nvkm_disp *, int);
+int gf119_dac_new(struct nvkm_disp *, int);
+
+int nv50_pior_new(struct nvkm_disp *, int);
+
+int nv50_sor_new(struct nvkm_disp *, int);
+int g84_sor_new(struct nvkm_disp *, int);
+int g94_sor_new(struct nvkm_disp *, int);
+int mcp77_sor_new(struct nvkm_disp *, int);
+int gt215_sor_new(struct nvkm_disp *, int);
+int mcp89_sor_new(struct nvkm_disp *, int);
+int gf119_sor_new(struct nvkm_disp *, int);
+int gk104_sor_new(struct nvkm_disp *, int);
+int gm107_sor_new(struct nvkm_disp *, int);
+int gm200_sor_new(struct nvkm_disp *, int);
+#endif
diff --git a/drivers/gpu/drm/nouveau/nvkm/engine/disp/mcp77.c b/drivers/gpu/drm/nouveau/nvkm/engine/disp/mcp77.c
new file mode 100644
index 000000000000..d7e0fbb12bf1
--- /dev/null
+++ b/drivers/gpu/drm/nouveau/nvkm/engine/disp/mcp77.c
@@ -0,0 +1,43 @@
+/*
+ * Copyright 2017 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.
+ */
+#include "nv50.h"
+#include "head.h"
+#include "ior.h"
+#include "rootnv50.h"
+
+static const struct nv50_disp_func
+mcp77_disp = {
+	.intr = nv50_disp_intr,
+	.uevent = &nv50_disp_chan_uevent,
+	.super = nv50_disp_super,
+	.root = &g94_disp_root_oclass,
+	.head.new = nv50_head_new,
+	.dac = { .nr = 3, .new = nv50_dac_new },
+	.sor = { .nr = 4, .new = mcp77_sor_new },
+	.pior = { .nr = 3, .new = nv50_pior_new },
+};
+
+int
+mcp77_disp_new(struct nvkm_device *device, int index, struct nvkm_disp **pdisp)
+{
+	return nv50_disp_new_(&mcp77_disp, device, index, 2, pdisp);
+}
diff --git a/drivers/gpu/drm/nouveau/nvkm/engine/disp/mcp89.c b/drivers/gpu/drm/nouveau/nvkm/engine/disp/mcp89.c
new file mode 100644
index 000000000000..7b75c57c12ed
--- /dev/null
+++ b/drivers/gpu/drm/nouveau/nvkm/engine/disp/mcp89.c
@@ -0,0 +1,43 @@
+/*
+ * Copyright 2017 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.
+ */
+#include "nv50.h"
+#include "head.h"
+#include "ior.h"
+#include "rootnv50.h"
+
+static const struct nv50_disp_func
+mcp89_disp = {
+	.intr = nv50_disp_intr,
+	.uevent = &nv50_disp_chan_uevent,
+	.super = nv50_disp_super,
+	.root = &gt215_disp_root_oclass,
+	.head.new = nv50_head_new,
+	.dac = { .nr = 3, .new = nv50_dac_new },
+	.sor = { .nr = 4, .new = mcp89_sor_new },
+	.pior = { .nr = 3, .new = nv50_pior_new },
+};
+
+int
+mcp89_disp_new(struct nvkm_device *device, int index, struct nvkm_disp **pdisp)
+{
+	return nv50_disp_new_(&mcp89_disp, device, index, 2, pdisp);
+}
diff --git a/drivers/gpu/drm/nouveau/nvkm/engine/disp/nv04.c b/drivers/gpu/drm/nouveau/nvkm/engine/disp/nv04.c
index 67254ce6f83f..b780ba1a3bc7 100644
--- a/drivers/gpu/drm/nouveau/nvkm/engine/disp/nv04.c
+++ b/drivers/gpu/drm/nouveau/nvkm/engine/disp/nv04.c
@@ -22,6 +22,7 @@
  * Authors: Ben Skeggs
  */
 #include "priv.h"
+#include "head.h"
 
 static const struct nvkm_disp_oclass *
 nv04_disp_root(struct nvkm_disp *disp)
@@ -30,20 +31,6 @@ nv04_disp_root(struct nvkm_disp *disp)
 }
 
 static void
-nv04_disp_vblank_init(struct nvkm_disp *disp, int head)
-{
-	struct nvkm_device *device = disp->engine.subdev.device;
-	nvkm_wr32(device, 0x600140 + (head * 0x2000) , 0x00000001);
-}
-
-static void
-nv04_disp_vblank_fini(struct nvkm_disp *disp, int head)
-{
-	struct nvkm_device *device = disp->engine.subdev.device;
-	nvkm_wr32(device, 0x600140 + (head * 0x2000) , 0x00000000);
-}
-
-static void
 nv04_disp_intr(struct nvkm_disp *disp)
 {
 	struct nvkm_subdev *subdev = &disp->engine.subdev;
@@ -74,12 +61,22 @@ static const struct nvkm_disp_func
 nv04_disp = {
 	.intr = nv04_disp_intr,
 	.root = nv04_disp_root,
-	.head.vblank_init = nv04_disp_vblank_init,
-	.head.vblank_fini = nv04_disp_vblank_fini,
 };
 
 int
 nv04_disp_new(struct nvkm_device *device, int index, struct nvkm_disp **pdisp)
 {
-	return nvkm_disp_new_(&nv04_disp, device, index, 2, pdisp);
+	int ret, i;
+
+	ret = nvkm_disp_new_(&nv04_disp, device, index, pdisp);
+	if (ret)
+		return ret;
+
+	for (i = 0; i < 2; i++) {
+		ret = nv04_head_new(*pdisp, i);
+		if (ret)
+			return ret;
+	}
+
+	return 0;
 }
diff --git a/drivers/gpu/drm/nouveau/nvkm/engine/disp/nv50.c b/drivers/gpu/drm/nouveau/nvkm/engine/disp/nv50.c
index 0db8efbf1c2e..0c570dbd3021 100644
--- a/drivers/gpu/drm/nouveau/nvkm/engine/disp/nv50.c
+++ b/drivers/gpu/drm/nouveau/nvkm/engine/disp/nv50.c
@@ -22,6 +22,8 @@
  * Authors: Ben Skeggs
  */
 #include "nv50.h"
+#include "head.h"
+#include "ior.h"
 #include "rootnv50.h"
 
 #include <core/client.h>
@@ -40,77 +42,6 @@ nv50_disp_root_(struct nvkm_disp *base)
 	return nv50_disp(base)->func->root;
 }
 
-static int
-nv50_disp_outp_internal_crt_(struct nvkm_disp *base, int index,
-			     struct dcb_output *dcb, struct nvkm_output **poutp)
-{
-	struct nv50_disp *disp = nv50_disp(base);
-	return disp->func->outp.internal.crt(base, index, dcb, poutp);
-}
-
-static int
-nv50_disp_outp_internal_tmds_(struct nvkm_disp *base, int index,
-			      struct dcb_output *dcb,
-			      struct nvkm_output **poutp)
-{
-	struct nv50_disp *disp = nv50_disp(base);
-	return disp->func->outp.internal.tmds(base, index, dcb, poutp);
-}
-
-static int
-nv50_disp_outp_internal_lvds_(struct nvkm_disp *base, int index,
-			      struct dcb_output *dcb,
-			      struct nvkm_output **poutp)
-{
-	struct nv50_disp *disp = nv50_disp(base);
-	return disp->func->outp.internal.lvds(base, index, dcb, poutp);
-}
-
-static int
-nv50_disp_outp_internal_dp_(struct nvkm_disp *base, int index,
-			    struct dcb_output *dcb, struct nvkm_output **poutp)
-{
-	struct nv50_disp *disp = nv50_disp(base);
-	if (disp->func->outp.internal.dp)
-		return disp->func->outp.internal.dp(base, index, dcb, poutp);
-	return -ENODEV;
-}
-
-static int
-nv50_disp_outp_external_tmds_(struct nvkm_disp *base, int index,
-			      struct dcb_output *dcb,
-			      struct nvkm_output **poutp)
-{
-	struct nv50_disp *disp = nv50_disp(base);
-	if (disp->func->outp.external.tmds)
-		return disp->func->outp.external.tmds(base, index, dcb, poutp);
-	return -ENODEV;
-}
-
-static int
-nv50_disp_outp_external_dp_(struct nvkm_disp *base, int index,
-			    struct dcb_output *dcb, struct nvkm_output **poutp)
-{
-	struct nv50_disp *disp = nv50_disp(base);
-	if (disp->func->outp.external.dp)
-		return disp->func->outp.external.dp(base, index, dcb, poutp);
-	return -ENODEV;
-}
-
-static void
-nv50_disp_vblank_fini_(struct nvkm_disp *base, int head)
-{
-	struct nv50_disp *disp = nv50_disp(base);
-	disp->func->head.vblank_fini(disp, head);
-}
-
-static void
-nv50_disp_vblank_init_(struct nvkm_disp *base, int head)
-{
-	struct nv50_disp *disp = nv50_disp(base);
-	disp->func->head.vblank_init(disp, head);
-}
-
 static void
 nv50_disp_intr_(struct nvkm_disp *base)
 {
@@ -123,6 +54,8 @@ nv50_disp_dtor_(struct nvkm_disp *base)
 {
 	struct nv50_disp *disp = nv50_disp(base);
 	nvkm_event_fini(&disp->uevent);
+	if (disp->wq)
+		destroy_workqueue(disp->wq);
 	return disp;
 }
 
@@ -131,14 +64,6 @@ nv50_disp_ = {
 	.dtor = nv50_disp_dtor_,
 	.intr = nv50_disp_intr_,
 	.root = nv50_disp_root_,
-	.outp.internal.crt = nv50_disp_outp_internal_crt_,
-	.outp.internal.tmds = nv50_disp_outp_internal_tmds_,
-	.outp.internal.lvds = nv50_disp_outp_internal_lvds_,
-	.outp.internal.dp = nv50_disp_outp_internal_dp_,
-	.outp.external.tmds = nv50_disp_outp_external_tmds_,
-	.outp.external.dp = nv50_disp_outp_external_dp_,
-	.head.vblank_init = nv50_disp_vblank_init_,
-	.head.vblank_fini = nv50_disp_vblank_fini_,
 };
 
 int
@@ -146,517 +71,227 @@ nv50_disp_new_(const struct nv50_disp_func *func, struct nvkm_device *device,
 	       int index, int heads, struct nvkm_disp **pdisp)
 {
 	struct nv50_disp *disp;
-	int ret;
+	int ret, i;
 
 	if (!(disp = kzalloc(sizeof(*disp), GFP_KERNEL)))
 		return -ENOMEM;
-	INIT_WORK(&disp->supervisor, func->super);
 	disp->func = func;
 	*pdisp = &disp->base;
 
-	ret = nvkm_disp_ctor(&nv50_disp_, device, index, heads, &disp->base);
+	ret = nvkm_disp_ctor(&nv50_disp_, device, index, &disp->base);
 	if (ret)
 		return ret;
 
-	return nvkm_event_init(func->uevent, 1, 1 + (heads * 4), &disp->uevent);
-}
-
-void
-nv50_disp_vblank_fini(struct nv50_disp *disp, int head)
-{
-	struct nvkm_device *device = disp->base.engine.subdev.device;
-	nvkm_mask(device, 0x61002c, (4 << head), 0);
-}
-
-void
-nv50_disp_vblank_init(struct nv50_disp *disp, int head)
-{
-	struct nvkm_device *device = disp->base.engine.subdev.device;
-	nvkm_mask(device, 0x61002c, (4 << head), (4 << head));
-}
-
-static const struct nvkm_enum
-nv50_disp_intr_error_type[] = {
-	{ 3, "ILLEGAL_MTHD" },
-	{ 4, "INVALID_VALUE" },
-	{ 5, "INVALID_STATE" },
-	{ 7, "INVALID_HANDLE" },
-	{}
-};
-
-static const struct nvkm_enum
-nv50_disp_intr_error_code[] = {
-	{ 0x00, "" },
-	{}
-};
-
-static void
-nv50_disp_intr_error(struct nv50_disp *disp, int chid)
-{
-	struct nvkm_subdev *subdev = &disp->base.engine.subdev;
-	struct nvkm_device *device = subdev->device;
-	u32 data = nvkm_rd32(device, 0x610084 + (chid * 0x08));
-	u32 addr = nvkm_rd32(device, 0x610080 + (chid * 0x08));
-	u32 code = (addr & 0x00ff0000) >> 16;
-	u32 type = (addr & 0x00007000) >> 12;
-	u32 mthd = (addr & 0x00000ffc);
-	const struct nvkm_enum *ec, *et;
-
-	et = nvkm_enum_find(nv50_disp_intr_error_type, type);
-	ec = nvkm_enum_find(nv50_disp_intr_error_code, code);
-
-	nvkm_error(subdev,
-		   "ERROR %d [%s] %02x [%s] chid %d mthd %04x data %08x\n",
-		   type, et ? et->name : "", code, ec ? ec->name : "",
-		   chid, mthd, data);
-
-	if (chid < ARRAY_SIZE(disp->chan)) {
-		switch (mthd) {
-		case 0x0080:
-			nv50_disp_chan_mthd(disp->chan[chid], NV_DBG_ERROR);
-			break;
-		default:
-			break;
-		}
-	}
-
-	nvkm_wr32(device, 0x610020, 0x00010000 << chid);
-	nvkm_wr32(device, 0x610080 + (chid * 0x08), 0x90000000);
-}
-
-static struct nvkm_output *
-exec_lookup(struct nv50_disp *disp, int head, int or, u32 ctrl,
-	    u32 *data, u8 *ver, u8 *hdr, u8 *cnt, u8 *len,
-	    struct nvbios_outp *info)
-{
-	struct nvkm_subdev *subdev = &disp->base.engine.subdev;
-	struct nvkm_bios *bios = subdev->device->bios;
-	struct nvkm_output *outp;
-	u16 mask, type;
-
-	if (or < 4) {
-		type = DCB_OUTPUT_ANALOG;
-		mask = 0;
-	} else
-	if (or < 8) {
-		switch (ctrl & 0x00000f00) {
-		case 0x00000000: type = DCB_OUTPUT_LVDS; mask = 1; break;
-		case 0x00000100: type = DCB_OUTPUT_TMDS; mask = 1; break;
-		case 0x00000200: type = DCB_OUTPUT_TMDS; mask = 2; break;
-		case 0x00000500: type = DCB_OUTPUT_TMDS; mask = 3; break;
-		case 0x00000800: type = DCB_OUTPUT_DP; mask = 1; break;
-		case 0x00000900: type = DCB_OUTPUT_DP; mask = 2; break;
-		default:
-			nvkm_error(subdev, "unknown SOR mc %08x\n", ctrl);
-			return NULL;
-		}
-		or  -= 4;
-	} else {
-		or   = or - 8;
-		type = 0x0010;
-		mask = 0;
-		switch (ctrl & 0x00000f00) {
-		case 0x00000000: type |= disp->pior.type[or]; break;
-		default:
-			nvkm_error(subdev, "unknown PIOR mc %08x\n", ctrl);
-			return NULL;
-		}
-	}
+	disp->wq = create_singlethread_workqueue("nvkm-disp");
+	if (!disp->wq)
+		return -ENOMEM;
+	INIT_WORK(&disp->supervisor, func->super);
 
-	mask  = 0x00c0 & (mask << 6);
-	mask |= 0x0001 << or;
-	mask |= 0x0100 << head;
-
-	list_for_each_entry(outp, &disp->base.outp, head) {
-		if ((outp->info.hasht & 0xff) == type &&
-		    (outp->info.hashm & mask) == mask) {
-			*data = nvbios_outp_match(bios, outp->info.hasht, mask,
-						  ver, hdr, cnt, len, info);
-			if (!*data)
-				return NULL;
-			return outp;
-		}
+	for (i = 0; func->head.new && i < heads; i++) {
+		ret = func->head.new(&disp->base, i);
+		if (ret)
+			return ret;
 	}
 
-	return NULL;
-}
-
-static struct nvkm_output *
-exec_script(struct nv50_disp *disp, int head, int id)
-{
-	struct nvkm_subdev *subdev = &disp->base.engine.subdev;
-	struct nvkm_device *device = subdev->device;
-	struct nvkm_bios *bios = device->bios;
-	struct nvkm_output *outp;
-	struct nvbios_outp info;
-	u8  ver, hdr, cnt, len;
-	u32 data, ctrl = 0;
-	u32 reg;
-	int i;
-
-	/* DAC */
-	for (i = 0; !(ctrl & (1 << head)) && i < disp->func->dac.nr; i++)
-		ctrl = nvkm_rd32(device, 0x610b5c + (i * 8));
-
-	/* SOR */
-	if (!(ctrl & (1 << head))) {
-		if (device->chipset  < 0x90 ||
-		    device->chipset == 0x92 ||
-		    device->chipset == 0xa0) {
-			reg = 0x610b74;
-		} else {
-			reg = 0x610798;
-		}
-		for (i = 0; !(ctrl & (1 << head)) && i < disp->func->sor.nr; i++)
-			ctrl = nvkm_rd32(device, reg + (i * 8));
-		i += 4;
+	for (i = 0; func->dac.new && i < func->dac.nr; i++) {
+		ret = func->dac.new(&disp->base, i);
+		if (ret)
+			return ret;
 	}
 
-	/* PIOR */
-	if (!(ctrl & (1 << head))) {
-		for (i = 0; !(ctrl & (1 << head)) && i < disp->func->pior.nr; i++)
-			ctrl = nvkm_rd32(device, 0x610b84 + (i * 8));
-		i += 8;
+	for (i = 0; func->pior.new && i < func->pior.nr; i++) {
+		ret = func->pior.new(&disp->base, i);
+		if (ret)
+			return ret;
 	}
 
-	if (!(ctrl & (1 << head)))
-		return NULL;
-	i--;
-
-	outp = exec_lookup(disp, head, i, ctrl, &data, &ver, &hdr, &cnt, &len, &info);
-	if (outp) {
-		struct nvbios_init init = {
-			.subdev = subdev,
-			.bios = bios,
-			.offset = info.script[id],
-			.outp = &outp->info,
-			.crtc = head,
-			.execute = 1,
-		};
-
-		nvbios_exec(&init);
+	for (i = 0; func->sor.new && i < func->sor.nr; i++) {
+		ret = func->sor.new(&disp->base, i);
+		if (ret)
+			return ret;
 	}
 
-	return outp;
+	return nvkm_event_init(func->uevent, 1, 1 + (heads * 4), &disp->uevent);
 }
 
-static struct nvkm_output *
-exec_clkcmp(struct nv50_disp *disp, int head, int id, u32 pclk, u32 *conf)
+static u32
+nv50_disp_super_iedt(struct nvkm_head *head, struct nvkm_outp *outp,
+		     u8 *ver, u8 *hdr, u8 *cnt, u8 *len,
+		     struct nvbios_outp *iedt)
 {
-	struct nvkm_subdev *subdev = &disp->base.engine.subdev;
-	struct nvkm_device *device = subdev->device;
-	struct nvkm_bios *bios = device->bios;
-	struct nvkm_output *outp;
-	struct nvbios_outp info1;
-	struct nvbios_ocfg info2;
-	u8  ver, hdr, cnt, len;
-	u32 data, ctrl = 0;
-	u32 reg;
-	int i;
-
-	/* DAC */
-	for (i = 0; !(ctrl & (1 << head)) && i < disp->func->dac.nr; i++)
-		ctrl = nvkm_rd32(device, 0x610b58 + (i * 8));
-
-	/* SOR */
-	if (!(ctrl & (1 << head))) {
-		if (device->chipset  < 0x90 ||
-		    device->chipset == 0x92 ||
-		    device->chipset == 0xa0) {
-			reg = 0x610b70;
-		} else {
-			reg = 0x610794;
-		}
-		for (i = 0; !(ctrl & (1 << head)) && i < disp->func->sor.nr; i++)
-			ctrl = nvkm_rd32(device, reg + (i * 8));
-		i += 4;
-	}
-
-	/* PIOR */
-	if (!(ctrl & (1 << head))) {
-		for (i = 0; !(ctrl & (1 << head)) && i < disp->func->pior.nr; i++)
-			ctrl = nvkm_rd32(device, 0x610b80 + (i * 8));
-		i += 8;
-	}
-
-	if (!(ctrl & (1 << head)))
-		return NULL;
-	i--;
-
-	outp = exec_lookup(disp, head, i, ctrl, &data, &ver, &hdr, &cnt, &len, &info1);
-	if (!outp)
-		return NULL;
-
-	*conf = (ctrl & 0x00000f00) >> 8;
-	if (outp->info.location == 0) {
-		switch (outp->info.type) {
-		case DCB_OUTPUT_TMDS:
-			if (*conf == 5)
-				*conf |= 0x0100;
-			break;
-		case DCB_OUTPUT_LVDS:
-			*conf |= disp->sor.lvdsconf;
-			break;
-		default:
-			break;
-		}
-	} else {
-		*conf = (ctrl & 0x00000f00) >> 8;
-		pclk = pclk / 2;
-	}
-
-	data = nvbios_ocfg_match(bios, data, *conf & 0xff, *conf >> 8,
-				 &ver, &hdr, &cnt, &len, &info2);
-	if (data && id < 0xff) {
-		data = nvbios_oclk_match(bios, info2.clkcmp[id], pclk);
-		if (data) {
-			struct nvbios_init init = {
-				.subdev = subdev,
-				.bios = bios,
-				.offset = data,
-				.outp = &outp->info,
-				.crtc = head,
-				.execute = 1,
-			};
-
-			nvbios_exec(&init);
-		}
-	}
-
-	return outp;
+	struct nvkm_bios *bios = head->disp->engine.subdev.device->bios;
+	const u8  l = ffs(outp->info.link);
+	const u16 t = outp->info.hasht;
+	const u16 m = (0x0100 << head->id) | (l << 6) | outp->info.or;
+	u32 data = nvbios_outp_match(bios, t, m, ver, hdr, cnt, len, iedt);
+	if (!data)
+		OUTP_DBG(outp, "missing IEDT for %04x:%04x", t, m);
+	return data;
 }
 
-static bool
-nv50_disp_dptmds_war(struct nvkm_device *device)
+static void
+nv50_disp_super_ied_on(struct nvkm_head *head,
+		       struct nvkm_ior *ior, int id, u32 khz)
 {
-	switch (device->chipset) {
-	case 0x94:
-	case 0x96:
-	case 0x98:
-		return true;
-	default:
-		break;
+	struct nvkm_subdev *subdev = &head->disp->engine.subdev;
+	struct nvkm_bios *bios = subdev->device->bios;
+	struct nvkm_outp *outp = ior->asy.outp;
+	struct nvbios_ocfg iedtrs;
+	struct nvbios_outp iedt;
+	u8  ver, hdr, cnt, len, flags = 0x00;
+	u32 data;
+
+	if (!outp) {
+		IOR_DBG(ior, "nothing to attach");
+		return;
 	}
-	return false;
-}
 
-static bool
-nv50_disp_dptmds_war_needed(struct nv50_disp *disp, struct dcb_output *outp)
-{
-	struct nvkm_device *device = disp->base.engine.subdev.device;
-	const u32 soff = __ffs(outp->or) * 0x800;
-	if (nv50_disp_dptmds_war(device) && outp->type == DCB_OUTPUT_TMDS) {
-		switch (nvkm_rd32(device, 0x614300 + soff) & 0x00030000) {
-		case 0x00000000:
-		case 0x00030000:
-			return true;
-		default:
-			break;
+	/* Lookup IED table for the device. */
+	data = nv50_disp_super_iedt(head, outp, &ver, &hdr, &cnt, &len, &iedt);
+	if (!data)
+		return;
+
+	/* Lookup IEDT runtime settings for the current configuration. */
+	if (ior->type == SOR) {
+		if (ior->asy.proto == LVDS) {
+			if (head->asy.or.depth == 24)
+				flags |= 0x02;
 		}
+		if (ior->asy.link == 3)
+			flags |= 0x01;
 	}
-	return false;
-
-}
-
-static void
-nv50_disp_dptmds_war_2(struct nv50_disp *disp, struct dcb_output *outp)
-{
-	struct nvkm_device *device = disp->base.engine.subdev.device;
-	const u32 soff = __ffs(outp->or) * 0x800;
 
-	if (!nv50_disp_dptmds_war_needed(disp, outp))
+	data = nvbios_ocfg_match(bios, data, ior->asy.proto_evo, flags,
+				 &ver, &hdr, &cnt, &len, &iedtrs);
+	if (!data) {
+		OUTP_DBG(outp, "missing IEDT RS for %02x:%02x",
+			 ior->asy.proto_evo, flags);
 		return;
-
-	nvkm_mask(device, 0x00e840, 0x80000000, 0x80000000);
-	nvkm_mask(device, 0x614300 + soff, 0x03000000, 0x03000000);
-	nvkm_mask(device, 0x61c10c + soff, 0x00000001, 0x00000001);
-
-	nvkm_mask(device, 0x61c00c + soff, 0x0f000000, 0x00000000);
-	nvkm_mask(device, 0x61c008 + soff, 0xff000000, 0x14000000);
-	nvkm_usec(device, 400, NVKM_DELAY);
-	nvkm_mask(device, 0x61c008 + soff, 0xff000000, 0x00000000);
-	nvkm_mask(device, 0x61c00c + soff, 0x0f000000, 0x01000000);
-
-	if (nvkm_rd32(device, 0x61c004 + soff) & 0x00000001) {
-		u32 seqctl = nvkm_rd32(device, 0x61c030 + soff);
-		u32  pu_pc = seqctl & 0x0000000f;
-		nvkm_wr32(device, 0x61c040 + soff + pu_pc * 4, 0x1f008000);
 	}
-}
 
-static void
-nv50_disp_dptmds_war_3(struct nv50_disp *disp, struct dcb_output *outp)
-{
-	struct nvkm_device *device = disp->base.engine.subdev.device;
-	const u32 soff = __ffs(outp->or) * 0x800;
-	u32 sorpwr;
-
-	if (!nv50_disp_dptmds_war_needed(disp, outp))
+	/* Execute the OnInt[23] script for the current frequency. */
+	data = nvbios_oclk_match(bios, iedtrs.clkcmp[id], khz);
+	if (!data) {
+		OUTP_DBG(outp, "missing IEDT RSS %d for %02x:%02x %d khz",
+			 id, ior->asy.proto_evo, flags, khz);
 		return;
-
-	sorpwr = nvkm_rd32(device, 0x61c004 + soff);
-	if (sorpwr & 0x00000001) {
-		u32 seqctl = nvkm_rd32(device, 0x61c030 + soff);
-		u32  pd_pc = (seqctl & 0x00000f00) >> 8;
-		u32  pu_pc =  seqctl & 0x0000000f;
-
-		nvkm_wr32(device, 0x61c040 + soff + pd_pc * 4, 0x1f008000);
-
-		nvkm_msec(device, 2000,
-			if (!(nvkm_rd32(device, 0x61c030 + soff) & 0x10000000))
-				break;
-		);
-		nvkm_mask(device, 0x61c004 + soff, 0x80000001, 0x80000000);
-		nvkm_msec(device, 2000,
-			if (!(nvkm_rd32(device, 0x61c030 + soff) & 0x10000000))
-				break;
-		);
-
-		nvkm_wr32(device, 0x61c040 + soff + pd_pc * 4, 0x00002000);
-		nvkm_wr32(device, 0x61c040 + soff + pu_pc * 4, 0x1f000000);
 	}
 
-	nvkm_mask(device, 0x61c10c + soff, 0x00000001, 0x00000000);
-	nvkm_mask(device, 0x614300 + soff, 0x03000000, 0x00000000);
-
-	if (sorpwr & 0x00000001) {
-		nvkm_mask(device, 0x61c004 + soff, 0x80000001, 0x80000001);
-	}
+	nvbios_init(subdev, data,
+		init.outp = &outp->info;
+		init.or   = ior->id;
+		init.link = ior->asy.link;
+		init.head = head->id;
+	);
 }
 
 static void
-nv50_disp_update_sppll1(struct nv50_disp *disp)
+nv50_disp_super_ied_off(struct nvkm_head *head, struct nvkm_ior *ior, int id)
 {
-	struct nvkm_device *device = disp->base.engine.subdev.device;
-	bool used = false;
-	int sor;
+	struct nvkm_outp *outp = ior->arm.outp;
+	struct nvbios_outp iedt;
+	u8  ver, hdr, cnt, len;
+	u32 data;
 
-	if (!nv50_disp_dptmds_war(device))
+	if (!outp) {
+		IOR_DBG(ior, "nothing attached");
 		return;
-
-	for (sor = 0; sor < disp->func->sor.nr; sor++) {
-		u32 clksor = nvkm_rd32(device, 0x614300 + (sor * 0x800));
-		switch (clksor & 0x03000000) {
-		case 0x02000000:
-		case 0x03000000:
-			used = true;
-			break;
-		default:
-			break;
-		}
 	}
 
-	if (used)
+	data = nv50_disp_super_iedt(head, outp, &ver, &hdr, &cnt, &len, &iedt);
+	if (!data)
 		return;
 
-	nvkm_mask(device, 0x00e840, 0x80000000, 0x00000000);
+	nvbios_init(&head->disp->engine.subdev, iedt.script[id],
+		init.outp = &outp->info;
+		init.or   = ior->id;
+		init.link = ior->arm.link;
+		init.head = head->id;
+	);
 }
 
-static void
-nv50_disp_intr_unk10_0(struct nv50_disp *disp, int head)
+static struct nvkm_ior *
+nv50_disp_super_ior_asy(struct nvkm_head *head)
 {
-	exec_script(disp, head, 1);
+	struct nvkm_ior *ior;
+	list_for_each_entry(ior, &head->disp->ior, head) {
+		if (ior->asy.head & (1 << head->id)) {
+			HEAD_DBG(head, "to %s", ior->name);
+			return ior;
+		}
+	}
+	HEAD_DBG(head, "nothing to attach");
+	return NULL;
 }
 
-static void
-nv50_disp_intr_unk20_0(struct nv50_disp *disp, int head)
+static struct nvkm_ior *
+nv50_disp_super_ior_arm(struct nvkm_head *head)
 {
-	struct nvkm_subdev *subdev = &disp->base.engine.subdev;
-	struct nvkm_output *outp = exec_script(disp, head, 2);
-
-	/* the binary driver does this outside of the supervisor handling
-	 * (after the third supervisor from a detach).  we (currently?)
-	 * allow both detach/attach to happen in the same set of
-	 * supervisor interrupts, so it would make sense to execute this
-	 * (full power down?) script after all the detach phases of the
-	 * supervisor handling.  like with training if needed from the
-	 * second supervisor, nvidia doesn't do this, so who knows if it's
-	 * entirely safe, but it does appear to work..
-	 *
-	 * without this script being run, on some configurations i've
-	 * seen, switching from DP to TMDS on a DP connector may result
-	 * in a blank screen (SOR_PWR off/on can restore it)
-	 */
-	if (outp && outp->info.type == DCB_OUTPUT_DP) {
-		struct nvkm_output_dp *outpdp = nvkm_output_dp(outp);
-		struct nvbios_init init = {
-			.subdev = subdev,
-			.bios = subdev->device->bios,
-			.outp = &outp->info,
-			.crtc = head,
-			.offset = outpdp->info.script[4],
-			.execute = 1,
-		};
-
-		nvkm_notify_put(&outpdp->irq);
-		nvbios_exec(&init);
-		atomic_set(&outpdp->lt.done, 0);
+	struct nvkm_ior *ior;
+	list_for_each_entry(ior, &head->disp->ior, head) {
+		if (ior->arm.head & (1 << head->id)) {
+			HEAD_DBG(head, "on %s", ior->name);
+			return ior;
+		}
 	}
+	HEAD_DBG(head, "nothing attached");
+	return NULL;
 }
 
-static void
-nv50_disp_intr_unk20_1(struct nv50_disp *disp, int head)
+void
+nv50_disp_super_3_0(struct nv50_disp *disp, struct nvkm_head *head)
 {
-	struct nvkm_device *device = disp->base.engine.subdev.device;
-	struct nvkm_devinit *devinit = device->devinit;
-	u32 pclk = nvkm_rd32(device, 0x610ad0 + (head * 0x540)) & 0x3fffff;
-	if (pclk)
-		nvkm_devinit_pll_set(devinit, PLL_VPLL0 + head, pclk);
+	struct nvkm_ior *ior;
+
+	/* Determine which OR, if any, we're attaching to the head. */
+	HEAD_DBG(head, "supervisor 3.0");
+	ior = nv50_disp_super_ior_asy(head);
+	if (!ior)
+		return;
+
+	/* Execute OnInt3 IED script. */
+	nv50_disp_super_ied_on(head, ior, 1, head->asy.hz / 1000);
+
+	/* OR-specific handling. */
+	if (ior->func->war_3)
+		ior->func->war_3(ior);
 }
 
 static void
-nv50_disp_intr_unk20_2_dp(struct nv50_disp *disp, int head,
-			  struct dcb_output *outp, u32 pclk)
+nv50_disp_super_2_2_dp(struct nvkm_head *head, struct nvkm_ior *ior)
 {
-	struct nvkm_subdev *subdev = &disp->base.engine.subdev;
-	struct nvkm_device *device = subdev->device;
-	const int link = !(outp->sorconf.link & 1);
-	const int   or = ffs(outp->or) - 1;
-	const u32 soff = (  or * 0x800);
-	const u32 loff = (link * 0x080) + soff;
-	const u32 ctrl = nvkm_rd32(device, 0x610794 + (or * 8));
-	const u32 symbol = 100000;
-	const s32 vactive = nvkm_rd32(device, 0x610af8 + (head * 0x540)) & 0xffff;
-	const s32 vblanke = nvkm_rd32(device, 0x610ae8 + (head * 0x540)) & 0xffff;
-	const s32 vblanks = nvkm_rd32(device, 0x610af0 + (head * 0x540)) & 0xffff;
-	u32 dpctrl = nvkm_rd32(device, 0x61c10c + loff);
-	u32 clksor = nvkm_rd32(device, 0x614300 + soff);
+	struct nvkm_subdev *subdev = &head->disp->engine.subdev;
+	const u32      khz = head->asy.hz / 1000;
+	const u32 linkKBps = ior->dp.bw * 27000;
+	const u32   symbol = 100000;
 	int bestTU = 0, bestVTUi = 0, bestVTUf = 0, bestVTUa = 0;
 	int TU, VTUi, VTUf, VTUa;
 	u64 link_data_rate, link_ratio, unk;
 	u32 best_diff = 64 * symbol;
-	u32 link_nr, link_bw, bits;
-	u64 value;
-
-	link_bw = (clksor & 0x000c0000) ? 270000 : 162000;
-	link_nr = hweight32(dpctrl & 0x000f0000);
+	u64 h, v;
 
 	/* symbols/hblank - algorithm taken from comments in tegra driver */
-	value = vblanke + vactive - vblanks - 7;
-	value = value * link_bw;
-	do_div(value, pclk);
-	value = value - (3 * !!(dpctrl & 0x00004000)) - (12 / link_nr);
-	nvkm_mask(device, 0x61c1e8 + soff, 0x0000ffff, value);
+	h = head->asy.hblanke + head->asy.htotal - head->asy.hblanks - 7;
+	h = h * linkKBps;
+	do_div(h, khz);
+	h = h - (3 * ior->dp.ef) - (12 / ior->dp.nr);
 
 	/* symbols/vblank - algorithm taken from comments in tegra driver */
-	value = vblanks - vblanke - 25;
-	value = value * link_bw;
-	do_div(value, pclk);
-	value = value - ((36 / link_nr) + 3) - 1;
-	nvkm_mask(device, 0x61c1ec + soff, 0x00ffffff, value);
+	v = head->asy.vblanks - head->asy.vblanke - 25;
+	v = v * linkKBps;
+	do_div(v, khz);
+	v = v - ((36 / ior->dp.nr) + 3) - 1;
 
-	/* watermark / activesym */
-	if      ((ctrl & 0xf0000) == 0x60000) bits = 30;
-	else if ((ctrl & 0xf0000) == 0x50000) bits = 24;
-	else                                  bits = 18;
+	ior->func->dp.audio_sym(ior, head->id, h, v);
 
-	link_data_rate = (pclk * bits / 8) / link_nr;
+	/* watermark / activesym */
+	link_data_rate = (khz * head->asy.or.depth / 8) / ior->dp.nr;
 
 	/* calculate ratio of packed data rate to link symbol rate */
 	link_ratio = link_data_rate * symbol;
-	do_div(link_ratio, link_bw);
+	do_div(link_ratio, linkKBps);
 
-	for (TU = 64; TU >= 32; TU--) {
+	for (TU = 64; ior->func->dp.activesym && TU >= 32; TU--) {
 		/* calculate average number of valid symbols in each TU */
 		u32 tu_valid = link_ratio * TU;
 		u32 calc, diff;
@@ -707,9 +342,15 @@ nv50_disp_intr_unk20_2_dp(struct nv50_disp *disp, int head,
 		}
 	}
 
-	if (!bestTU) {
-		nvkm_error(subdev, "unable to find suitable dp config\n");
-		return;
+	if (ior->func->dp.activesym) {
+		if (!bestTU) {
+			nvkm_error(subdev, "unable to determine dp config\n");
+			return;
+		}
+		ior->func->dp.activesym(ior, head->id, bestTU,
+					bestVTUa, bestVTUf, bestVTUi);
+	} else {
+		bestTU = 64;
 	}
 
 	/* XXX close to vbios numbers, but not right */
@@ -719,190 +360,223 @@ nv50_disp_intr_unk20_2_dp(struct nv50_disp *disp, int head,
 	do_div(unk, symbol);
 	unk += 6;
 
-	nvkm_mask(device, 0x61c10c + loff, 0x000001fc, bestTU << 2);
-	nvkm_mask(device, 0x61c128 + loff, 0x010f7f3f, bestVTUa << 24 |
-						   bestVTUf << 16 |
-						   bestVTUi << 8 | unk);
+	ior->func->dp.watermark(ior, head->id, unk);
 }
 
-static void
-nv50_disp_intr_unk20_2(struct nv50_disp *disp, int head)
+void
+nv50_disp_super_2_2(struct nv50_disp *disp, struct nvkm_head *head)
 {
-	struct nvkm_device *device = disp->base.engine.subdev.device;
-	struct nvkm_output *outp;
-	u32 pclk = nvkm_rd32(device, 0x610ad0 + (head * 0x540)) & 0x3fffff;
-	u32 hval, hreg = 0x614200 + (head * 0x800);
-	u32 oval, oreg;
-	u32 mask, conf;
-
-	outp = exec_clkcmp(disp, head, 0xff, pclk, &conf);
-	if (!outp)
+	const u32 khz = head->asy.hz / 1000;
+	struct nvkm_outp *outp;
+	struct nvkm_ior *ior;
+
+	/* Determine which OR, if any, we're attaching from the head. */
+	HEAD_DBG(head, "supervisor 2.2");
+	ior = nv50_disp_super_ior_asy(head);
+	if (!ior)
 		return;
 
-	/* we allow both encoder attach and detach operations to occur
-	 * within a single supervisor (ie. modeset) sequence.  the
-	 * encoder detach scripts quite often switch off power to the
-	 * lanes, which requires the link to be re-trained.
-	 *
-	 * this is not generally an issue as the sink "must" (heh)
-	 * signal an irq when it's lost sync so the driver can
-	 * re-train.
+	/* For some reason, NVIDIA decided not to:
 	 *
-	 * however, on some boards, if one does not configure at least
-	 * the gpu side of the link *before* attaching, then various
-	 * things can go horribly wrong (PDISP disappearing from mmio,
-	 * third supervisor never happens, etc).
+	 * A) Give dual-link LVDS a separate EVO protocol, like for TMDS.
+	 *  and
+	 * B) Use SetControlOutputResource.PixelDepth on LVDS.
 	 *
-	 * the solution is simply to retrain here, if necessary.  last
-	 * i checked, the binary driver userspace does not appear to
-	 * trigger this situation (it forces an UPDATE between steps).
+	 * Override the values we usually read from HW with the same
+	 * data we pass though an ioctl instead.
 	 */
-	if (outp->info.type == DCB_OUTPUT_DP) {
-		u32 soff = (ffs(outp->info.or) - 1) * 0x08;
-		u32 ctrl, datarate;
-
-		if (outp->info.location == 0) {
-			ctrl = nvkm_rd32(device, 0x610794 + soff);
-			soff = 1;
-		} else {
-			ctrl = nvkm_rd32(device, 0x610b80 + soff);
-			soff = 2;
-		}
+	if (ior->type == SOR && ior->asy.proto == LVDS) {
+		head->asy.or.depth = (disp->sor.lvdsconf & 0x0200) ? 24 : 18;
+		ior->asy.link      = (disp->sor.lvdsconf & 0x0100) ? 3  : 1;
+	}
 
-		switch ((ctrl & 0x000f0000) >> 16) {
-		case 6: datarate = pclk * 30; break;
-		case 5: datarate = pclk * 24; break;
-		case 2:
-		default:
-			datarate = pclk * 18;
-			break;
-		}
+	/* Handle any link training, etc. */
+	if ((outp = ior->asy.outp) && outp->func->acquire)
+		outp->func->acquire(outp);
 
-		if (nvkm_output_dp_train(outp, datarate / soff))
-			OUTP_ERR(outp, "link not trained before attach");
-	}
+	/* Execute OnInt2 IED script. */
+	nv50_disp_super_ied_on(head, ior, 0, khz);
 
-	exec_clkcmp(disp, head, 0, pclk, &conf);
+	/* Program RG clock divider. */
+	head->func->rgclk(head, ior->asy.rgdiv);
 
-	if (!outp->info.location && outp->info.type == DCB_OUTPUT_ANALOG) {
-		oreg = 0x614280 + (ffs(outp->info.or) - 1) * 0x800;
-		oval = 0x00000000;
-		hval = 0x00000000;
-		mask = 0xffffffff;
-	} else
-	if (!outp->info.location) {
-		if (outp->info.type == DCB_OUTPUT_DP)
-			nv50_disp_intr_unk20_2_dp(disp, head, &outp->info, pclk);
-		oreg = 0x614300 + (ffs(outp->info.or) - 1) * 0x800;
-		oval = (conf & 0x0100) ? 0x00000101 : 0x00000000;
-		hval = 0x00000000;
-		mask = 0x00000707;
-	} else {
-		oreg = 0x614380 + (ffs(outp->info.or) - 1) * 0x800;
-		oval = 0x00000001;
-		hval = 0x00000001;
-		mask = 0x00000707;
-	}
+	/* Mode-specific internal DP configuration. */
+	if (ior->type == SOR && ior->asy.proto == DP)
+		nv50_disp_super_2_2_dp(head, ior);
 
-	nvkm_mask(device, hreg, 0x0000000f, hval);
-	nvkm_mask(device, oreg, mask, oval);
+	/* OR-specific handling. */
+	ior->func->clock(ior);
+	if (ior->func->war_2)
+		ior->func->war_2(ior);
+}
 
-	nv50_disp_dptmds_war_2(disp, &outp->info);
+void
+nv50_disp_super_2_1(struct nv50_disp *disp, struct nvkm_head *head)
+{
+	struct nvkm_devinit *devinit = disp->base.engine.subdev.device->devinit;
+	const u32 khz = head->asy.hz / 1000;
+	HEAD_DBG(head, "supervisor 2.1 - %d khz", khz);
+	if (khz)
+		nvkm_devinit_pll_set(devinit, PLL_VPLL0 + head->id, khz);
 }
 
-/* If programming a TMDS output on a SOR that can also be configured for
- * DisplayPort, make sure NV50_SOR_DP_CTRL_ENABLE is forced off.
- *
- * It looks like the VBIOS TMDS scripts make an attempt at this, however,
- * the VBIOS scripts on at least one board I have only switch it off on
- * link 0, causing a blank display if the output has previously been
- * programmed for DisplayPort.
- */
-static void
-nv50_disp_intr_unk40_0_tmds(struct nv50_disp *disp,
-			    struct dcb_output *outp)
+void
+nv50_disp_super_2_0(struct nv50_disp *disp, struct nvkm_head *head)
 {
-	struct nvkm_device *device = disp->base.engine.subdev.device;
-	struct nvkm_bios *bios = device->bios;
-	const int link = !(outp->sorconf.link & 1);
-	const int   or = ffs(outp->or) - 1;
-	const u32 loff = (or * 0x800) + (link * 0x80);
-	const u16 mask = (outp->sorconf.link << 6) | outp->or;
-	struct dcb_output match;
-	u8  ver, hdr;
-
-	if (dcb_outp_match(bios, DCB_OUTPUT_DP, mask, &ver, &hdr, &match))
-		nvkm_mask(device, 0x61c10c + loff, 0x00000001, 0x00000000);
+	struct nvkm_outp *outp;
+	struct nvkm_ior *ior;
+
+	/* Determine which OR, if any, we're detaching from the head. */
+	HEAD_DBG(head, "supervisor 2.0");
+	ior = nv50_disp_super_ior_arm(head);
+	if (!ior)
+		return;
+
+	/* Execute OffInt2 IED script. */
+	nv50_disp_super_ied_off(head, ior, 2);
+
+	/* If we're shutting down the OR's only active head, execute
+	 * the output path's release function.
+	 */
+	if (ior->arm.head == (1 << head->id)) {
+		if ((outp = ior->arm.outp) && outp->func->release)
+			outp->func->release(outp, ior);
+	}
 }
 
-static void
-nv50_disp_intr_unk40_0(struct nv50_disp *disp, int head)
+void
+nv50_disp_super_1_0(struct nv50_disp *disp, struct nvkm_head *head)
 {
-	struct nvkm_device *device = disp->base.engine.subdev.device;
-	struct nvkm_output *outp;
-	u32 pclk = nvkm_rd32(device, 0x610ad0 + (head * 0x540)) & 0x3fffff;
-	u32 conf;
+	struct nvkm_ior *ior;
 
-	outp = exec_clkcmp(disp, head, 1, pclk, &conf);
-	if (!outp)
+	/* Determine which OR, if any, we're detaching from the head. */
+	HEAD_DBG(head, "supervisor 1.0");
+	ior = nv50_disp_super_ior_arm(head);
+	if (!ior)
 		return;
 
-	if (outp->info.location == 0 && outp->info.type == DCB_OUTPUT_TMDS)
-		nv50_disp_intr_unk40_0_tmds(disp, &outp->info);
-	nv50_disp_dptmds_war_3(disp, &outp->info);
+	/* Execute OffInt1 IED script. */
+	nv50_disp_super_ied_off(head, ior, 1);
 }
 
 void
-nv50_disp_intr_supervisor(struct work_struct *work)
+nv50_disp_super_1(struct nv50_disp *disp)
+{
+	struct nvkm_head *head;
+	struct nvkm_ior *ior;
+
+	list_for_each_entry(head, &disp->base.head, head) {
+		head->func->state(head, &head->arm);
+		head->func->state(head, &head->asy);
+	}
+
+	list_for_each_entry(ior, &disp->base.ior, head) {
+		ior->func->state(ior, &ior->arm);
+		ior->func->state(ior, &ior->asy);
+	}
+}
+
+void
+nv50_disp_super(struct work_struct *work)
 {
 	struct nv50_disp *disp =
 		container_of(work, struct nv50_disp, supervisor);
 	struct nvkm_subdev *subdev = &disp->base.engine.subdev;
 	struct nvkm_device *device = subdev->device;
+	struct nvkm_head *head;
 	u32 super = nvkm_rd32(device, 0x610030);
-	int head;
 
 	nvkm_debug(subdev, "supervisor %08x %08x\n", disp->super, super);
 
 	if (disp->super & 0x00000010) {
 		nv50_disp_chan_mthd(disp->chan[0], NV_DBG_DEBUG);
-		for (head = 0; head < disp->base.head.nr; head++) {
-			if (!(super & (0x00000020 << head)))
+		nv50_disp_super_1(disp);
+		list_for_each_entry(head, &disp->base.head, head) {
+			if (!(super & (0x00000020 << head->id)))
 				continue;
-			if (!(super & (0x00000080 << head)))
+			if (!(super & (0x00000080 << head->id)))
 				continue;
-			nv50_disp_intr_unk10_0(disp, head);
+			nv50_disp_super_1_0(disp, head);
 		}
 	} else
 	if (disp->super & 0x00000020) {
-		for (head = 0; head < disp->base.head.nr; head++) {
-			if (!(super & (0x00000080 << head)))
+		list_for_each_entry(head, &disp->base.head, head) {
+			if (!(super & (0x00000080 << head->id)))
 				continue;
-			nv50_disp_intr_unk20_0(disp, head);
+			nv50_disp_super_2_0(disp, head);
 		}
-		for (head = 0; head < disp->base.head.nr; head++) {
-			if (!(super & (0x00000200 << head)))
+		nvkm_outp_route(&disp->base);
+		list_for_each_entry(head, &disp->base.head, head) {
+			if (!(super & (0x00000200 << head->id)))
 				continue;
-			nv50_disp_intr_unk20_1(disp, head);
+			nv50_disp_super_2_1(disp, head);
 		}
-		for (head = 0; head < disp->base.head.nr; head++) {
-			if (!(super & (0x00000080 << head)))
+		list_for_each_entry(head, &disp->base.head, head) {
+			if (!(super & (0x00000080 << head->id)))
 				continue;
-			nv50_disp_intr_unk20_2(disp, head);
+			nv50_disp_super_2_2(disp, head);
 		}
 	} else
 	if (disp->super & 0x00000040) {
-		for (head = 0; head < disp->base.head.nr; head++) {
-			if (!(super & (0x00000080 << head)))
+		list_for_each_entry(head, &disp->base.head, head) {
+			if (!(super & (0x00000080 << head->id)))
 				continue;
-			nv50_disp_intr_unk40_0(disp, head);
+			nv50_disp_super_3_0(disp, head);
 		}
-		nv50_disp_update_sppll1(disp);
 	}
 
 	nvkm_wr32(device, 0x610030, 0x80000000);
 }
 
+static const struct nvkm_enum
+nv50_disp_intr_error_type[] = {
+	{ 3, "ILLEGAL_MTHD" },
+	{ 4, "INVALID_VALUE" },
+	{ 5, "INVALID_STATE" },
+	{ 7, "INVALID_HANDLE" },
+	{}
+};
+
+static const struct nvkm_enum
+nv50_disp_intr_error_code[] = {
+	{ 0x00, "" },
+	{}
+};
+
+static void
+nv50_disp_intr_error(struct nv50_disp *disp, int chid)
+{
+	struct nvkm_subdev *subdev = &disp->base.engine.subdev;
+	struct nvkm_device *device = subdev->device;
+	u32 data = nvkm_rd32(device, 0x610084 + (chid * 0x08));
+	u32 addr = nvkm_rd32(device, 0x610080 + (chid * 0x08));
+	u32 code = (addr & 0x00ff0000) >> 16;
+	u32 type = (addr & 0x00007000) >> 12;
+	u32 mthd = (addr & 0x00000ffc);
+	const struct nvkm_enum *ec, *et;
+
+	et = nvkm_enum_find(nv50_disp_intr_error_type, type);
+	ec = nvkm_enum_find(nv50_disp_intr_error_code, code);
+
+	nvkm_error(subdev,
+		   "ERROR %d [%s] %02x [%s] chid %d mthd %04x data %08x\n",
+		   type, et ? et->name : "", code, ec ? ec->name : "",
+		   chid, mthd, data);
+
+	if (chid < ARRAY_SIZE(disp->chan)) {
+		switch (mthd) {
+		case 0x0080:
+			nv50_disp_chan_mthd(disp->chan[chid], NV_DBG_ERROR);
+			break;
+		default:
+			break;
+		}
+	}
+
+	nvkm_wr32(device, 0x610020, 0x00010000 << chid);
+	nvkm_wr32(device, 0x610080 + (chid * 0x08), 0x90000000);
+}
+
 void
 nv50_disp_intr(struct nv50_disp *disp)
 {
@@ -934,7 +608,7 @@ nv50_disp_intr(struct nv50_disp *disp)
 
 	if (intr1 & 0x00000070) {
 		disp->super = (intr1 & 0x00000070);
-		schedule_work(&disp->supervisor);
+		queue_work(disp->wq, &disp->supervisor);
 		nvkm_wr32(device, 0x610024, disp->super);
 	}
 }
@@ -943,23 +617,12 @@ static const struct nv50_disp_func
 nv50_disp = {
 	.intr = nv50_disp_intr,
 	.uevent = &nv50_disp_chan_uevent,
-	.super = nv50_disp_intr_supervisor,
+	.super = nv50_disp_super,
 	.root = &nv50_disp_root_oclass,
-	.head.vblank_init = nv50_disp_vblank_init,
-	.head.vblank_fini = nv50_disp_vblank_fini,
-	.head.scanoutpos = nv50_disp_root_scanoutpos,
-	.outp.internal.crt = nv50_dac_output_new,
-	.outp.internal.tmds = nv50_sor_output_new,
-	.outp.internal.lvds = nv50_sor_output_new,
-	.outp.external.tmds = nv50_pior_output_new,
-	.outp.external.dp = nv50_pior_dp_new,
-	.dac.nr = 3,
-	.dac.power = nv50_dac_power,
-	.dac.sense = nv50_dac_sense,
-	.sor.nr = 2,
-	.sor.power = nv50_sor_power,
-	.pior.nr = 3,
-	.pior.power = nv50_pior_power,
+	.head.new = nv50_head_new,
+	.dac = { .nr = 3, .new = nv50_dac_new },
+	.sor = { .nr = 2, .new = nv50_sor_new },
+	.pior = { .nr = 3, .new = nv50_pior_new },
 };
 
 int
diff --git a/drivers/gpu/drm/nouveau/nvkm/engine/disp/nv50.h b/drivers/gpu/drm/nouveau/nvkm/engine/disp/nv50.h
index 1e1de6bfe85a..19c635663399 100644
--- a/drivers/gpu/drm/nouveau/nvkm/engine/disp/nv50.h
+++ b/drivers/gpu/drm/nouveau/nvkm/engine/disp/nv50.h
@@ -2,18 +2,13 @@
 #define __NV50_DISP_H__
 #define nv50_disp(p) container_of((p), struct nv50_disp, base)
 #include "priv.h"
-struct nvkm_output;
-struct nvkm_output_dp;
-
-#define NV50_DISP_MTHD_ struct nvkm_object *object,                            \
-	struct nv50_disp *disp, void *data, u32 size
-#define NV50_DISP_MTHD_V0 NV50_DISP_MTHD_, int head
-#define NV50_DISP_MTHD_V1 NV50_DISP_MTHD_, int head, struct nvkm_output *outp
+struct nvkm_head;
 
 struct nv50_disp {
 	const struct nv50_disp_func *func;
 	struct nvkm_disp base;
 
+	struct workqueue_struct *wq;
 	struct work_struct supervisor;
 	u32 super;
 
@@ -30,42 +25,18 @@ struct nv50_disp {
 	struct nv50_disp_chan *chan[17];
 };
 
-int nv50_disp_root_scanoutpos(NV50_DISP_MTHD_V0);
-
-int gf119_disp_root_scanoutpos(NV50_DISP_MTHD_V0);
-
-int nv50_dac_power(NV50_DISP_MTHD_V1);
-int nv50_dac_sense(NV50_DISP_MTHD_V1);
-
-int gt215_hda_eld(NV50_DISP_MTHD_V1);
-int gf119_hda_eld(NV50_DISP_MTHD_V1);
-
-int g84_hdmi_ctrl(NV50_DISP_MTHD_V1);
-int gt215_hdmi_ctrl(NV50_DISP_MTHD_V1);
-int gf119_hdmi_ctrl(NV50_DISP_MTHD_V1);
-int gk104_hdmi_ctrl(NV50_DISP_MTHD_V1);
-
-int nv50_sor_power(NV50_DISP_MTHD_V1);
-int nv50_pior_power(NV50_DISP_MTHD_V1);
+void nv50_disp_super_1(struct nv50_disp *);
+void nv50_disp_super_1_0(struct nv50_disp *, struct nvkm_head *);
+void nv50_disp_super_2_0(struct nv50_disp *, struct nvkm_head *);
+void nv50_disp_super_2_1(struct nv50_disp *, struct nvkm_head *);
+void nv50_disp_super_2_2(struct nv50_disp *, struct nvkm_head *);
+void nv50_disp_super_3_0(struct nv50_disp *, struct nvkm_head *);
 
 int nv50_disp_new_(const struct nv50_disp_func *, struct nvkm_device *,
 		   int index, int heads, struct nvkm_disp **);
 int gf119_disp_new_(const struct nv50_disp_func *, struct nvkm_device *,
 		    int index, struct nvkm_disp **);
 
-struct nv50_disp_func_outp {
-	int (* crt)(struct nvkm_disp *, int index, struct dcb_output *,
-		    struct nvkm_output **);
-	int (*  tv)(struct nvkm_disp *, int index, struct dcb_output *,
-		    struct nvkm_output **);
-	int (*tmds)(struct nvkm_disp *, int index, struct dcb_output *,
-		    struct nvkm_output **);
-	int (*lvds)(struct nvkm_disp *, int index, struct dcb_output *,
-		    struct nvkm_output **);
-	int (*  dp)(struct nvkm_disp *, int index, struct dcb_output *,
-		    struct nvkm_output **);
-};
-
 struct nv50_disp_func {
 	void (*intr)(struct nv50_disp *);
 	void (*intr_error)(struct nv50_disp *, int chid);
@@ -76,44 +47,33 @@ struct nv50_disp_func {
 	const struct nvkm_disp_oclass *root;
 
 	struct {
-		void (*vblank_init)(struct nv50_disp *, int head);
-		void (*vblank_fini)(struct nv50_disp *, int head);
-		int (*scanoutpos)(NV50_DISP_MTHD_V0);
+		int (*new)(struct nvkm_disp *, int id);
 	} head;
 
 	struct {
-		const struct nv50_disp_func_outp internal;
-		const struct nv50_disp_func_outp external;
-	} outp;
-
-	struct {
 		int nr;
-		int (*power)(NV50_DISP_MTHD_V1);
-		int (*sense)(NV50_DISP_MTHD_V1);
+		int (*new)(struct nvkm_disp *, int id);
 	} dac;
 
 	struct {
 		int nr;
-		int (*power)(NV50_DISP_MTHD_V1);
-		int (*hda_eld)(NV50_DISP_MTHD_V1);
-		int (*hdmi)(NV50_DISP_MTHD_V1);
-		void (*magic)(struct nvkm_output *);
+		int (*new)(struct nvkm_disp *, int id);
 	} sor;
 
 	struct {
 		int nr;
-		int (*power)(NV50_DISP_MTHD_V1);
+		int (*new)(struct nvkm_disp *, int id);
 	} pior;
 };
 
-void nv50_disp_vblank_init(struct nv50_disp *, int);
-void nv50_disp_vblank_fini(struct nv50_disp *, int);
 void nv50_disp_intr(struct nv50_disp *);
-void nv50_disp_intr_supervisor(struct work_struct *);
+void nv50_disp_super(struct work_struct *);
 
-void gf119_disp_vblank_init(struct nv50_disp *, int);
-void gf119_disp_vblank_fini(struct nv50_disp *, int);
 void gf119_disp_intr(struct nv50_disp *);
-void gf119_disp_intr_supervisor(struct work_struct *);
+void gf119_disp_super(struct work_struct *);
 void gf119_disp_intr_error(struct nv50_disp *, int);
+
+void nv50_disp_dptmds_war_2(struct nv50_disp *, struct dcb_output *);
+void nv50_disp_dptmds_war_3(struct nv50_disp *, struct dcb_output *);
+void nv50_disp_update_sppll1(struct nv50_disp *);
 #endif
diff --git a/drivers/gpu/drm/nouveau/nvkm/engine/disp/oimmnv50.c b/drivers/gpu/drm/nouveau/nvkm/engine/disp/oimmnv50.c
index 07540f3d32dc..f3b0fa2c5924 100644
--- a/drivers/gpu/drm/nouveau/nvkm/engine/disp/oimmnv50.c
+++ b/drivers/gpu/drm/nouveau/nvkm/engine/disp/oimmnv50.c
@@ -22,6 +22,7 @@
  * Authors: Ben Skeggs
  */
 #include "channv50.h"
+#include "head.h"
 #include "rootnv50.h"
 
 #include <core/client.h>
@@ -48,7 +49,7 @@ nv50_disp_oimm_new(const struct nv50_disp_chan_func *func,
 	if (!(ret = nvif_unpack(ret, &data, &size, args->v0, 0, 0, false))) {
 		nvif_ioctl(parent, "create disp overlay vers %d head %d\n",
 			   args->v0.version, args->v0.head);
-		if (args->v0.head > disp->base.head.nr)
+		if (!nvkm_head_find(&disp->base, args->v0.head))
 			return -EINVAL;
 		head = args->v0.head;
 	} else
diff --git a/drivers/gpu/drm/nouveau/nvkm/engine/disp/outp.c b/drivers/gpu/drm/nouveau/nvkm/engine/disp/outp.c
index bbe5ec0dedb2..85aff85394ac 100644
--- a/drivers/gpu/drm/nouveau/nvkm/engine/disp/outp.c
+++ b/drivers/gpu/drm/nouveau/nvkm/engine/disp/outp.c
@@ -22,29 +22,207 @@
  * Authors: Ben Skeggs
  */
 #include "outp.h"
+#include "ior.h"
 
 #include <subdev/bios.h>
 #include <subdev/bios/dcb.h>
 #include <subdev/i2c.h>
 
 void
-nvkm_output_fini(struct nvkm_output *outp)
+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
+nvkm_outp_xlat(struct nvkm_outp *outp, enum nvkm_ior_type *type)
+{
+	switch (outp->info.location) {
+	case 0:
+		switch (outp->info.type) {
+		case DCB_OUTPUT_ANALOG: *type = DAC; return  CRT;
+		case DCB_OUTPUT_TMDS  : *type = SOR; return TMDS;
+		case DCB_OUTPUT_LVDS  : *type = SOR; return LVDS;
+		case DCB_OUTPUT_DP    : *type = SOR; return   DP;
+		default:
+			break;
+		}
+		break;
+	case 1:
+		switch (outp->info.type) {
+		case DCB_OUTPUT_TMDS: *type = PIOR; return TMDS;
+		case DCB_OUTPUT_DP  : *type = PIOR; return TMDS; /* not a bug */
+		default:
+			break;
+		}
+		break;
+	default:
+		break;
+	}
+	WARN_ON(1);
+	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->func->route.set || 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->func->route.set || ior->id == __ffs(outp->info.or)))
+			return nvkm_outp_acquire_ior(outp, user, ior);
+	}
+
+	return -ENOSPC;
+}
+
+void
+nvkm_outp_fini(struct nvkm_outp *outp)
 {
 	if (outp->func->fini)
 		outp->func->fini(outp);
 }
 
+static void
+nvkm_outp_init_route(struct nvkm_outp *outp)
+{
+	struct nvkm_disp *disp = outp->disp;
+	enum nvkm_ior_proto proto;
+	enum nvkm_ior_type type;
+	struct nvkm_ior *ior;
+	int id, link;
+
+	/* Find any OR from the class that is able to support this device. */
+	proto = nvkm_outp_xlat(outp, &type);
+	if (proto == UNKNOWN)
+		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. */
+	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. */
+		id   = ffs(outp->info.or) - 1;
+		link = (ior->type == SOR) ? outp->info.sorconf.link : 0;
+	}
+
+	ior = nvkm_ior_find(disp, type, id);
+	if (!ior) {
+		WARN_ON(1);
+		return;
+	}
+
+	/* 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
-nvkm_output_init(struct nvkm_output *outp)
+nvkm_outp_init(struct nvkm_outp *outp)
 {
+	nvkm_outp_init_route(outp);
 	if (outp->func->init)
 		outp->func->init(outp);
 }
 
 void
-nvkm_output_del(struct nvkm_output **poutp)
+nvkm_outp_del(struct nvkm_outp **poutp)
 {
-	struct nvkm_output *outp = *poutp;
+	struct nvkm_outp *outp = *poutp;
 	if (outp && !WARN_ON(!outp->func)) {
 		if (outp->func->dtor)
 			*poutp = outp->func->dtor(outp);
@@ -53,11 +231,13 @@ nvkm_output_del(struct nvkm_output **poutp)
 	}
 }
 
-void
-nvkm_output_ctor(const struct nvkm_output_func *func, struct nvkm_disp *disp,
-		 int index, struct dcb_output *dcbE, struct nvkm_output *outp)
+int
+nvkm_outp_ctor(const struct nvkm_outp_func *func, struct nvkm_disp *disp,
+	       int index, struct dcb_output *dcbE, struct nvkm_outp *outp)
 {
 	struct nvkm_i2c *i2c = disp->engine.subdev.device->i2c;
+	enum nvkm_ior_proto proto;
+	enum nvkm_ior_type type;
 
 	outp->func = func;
 	outp->disp = disp;
@@ -72,16 +252,24 @@ nvkm_output_ctor(const struct nvkm_output_func *func, struct nvkm_disp *disp,
 		 outp->info.type >= 2 ? outp->info.sorconf.link : 0,
 		 outp->info.connector, outp->info.i2c_index,
 		 outp->info.bus, outp->info.heads);
+
+	/* Cull output paths we can't map to an output resource. */
+	proto = nvkm_outp_xlat(outp, &type);
+	if (proto == UNKNOWN)
+		return -ENODEV;
+
+	return 0;
 }
 
+static const struct nvkm_outp_func
+nvkm_outp = {
+};
+
 int
-nvkm_output_new_(const struct nvkm_output_func *func,
-		 struct nvkm_disp *disp, int index, struct dcb_output *dcbE,
-		 struct nvkm_output **poutp)
+nvkm_outp_new(struct nvkm_disp *disp, int index, struct dcb_output *dcbE,
+	      struct nvkm_outp **poutp)
 {
 	if (!(*poutp = kzalloc(sizeof(**poutp), GFP_KERNEL)))
 		return -ENOMEM;
-
-	nvkm_output_ctor(func, disp, index, dcbE, *poutp);
-	return 0;
+	return nvkm_outp_ctor(&nvkm_outp, disp, index, dcbE, *poutp);
 }
diff --git a/drivers/gpu/drm/nouveau/nvkm/engine/disp/outp.h b/drivers/gpu/drm/nouveau/nvkm/engine/disp/outp.h
index 07727198d7ce..146d101d4891 100644
--- a/drivers/gpu/drm/nouveau/nvkm/engine/disp/outp.h
+++ b/drivers/gpu/drm/nouveau/nvkm/engine/disp/outp.h
@@ -5,47 +5,46 @@
 #include <subdev/bios.h>
 #include <subdev/bios/dcb.h>
 
-struct nvkm_output {
-	const struct nvkm_output_func *func;
+struct nvkm_outp {
+	const struct nvkm_outp_func *func;
 	struct nvkm_disp *disp;
 	int index;
 	struct dcb_output info;
 
-	// whatever (if anything) is pointed at by the dcb device entry
 	struct nvkm_i2c_bus *i2c;
 	int or;
 
 	struct list_head head;
-	struct nvkm_connector *conn;
-};
+	struct nvkm_conn *conn;
 
-struct nvkm_output_func {
-	void *(*dtor)(struct nvkm_output *);
-	void (*init)(struct nvkm_output *);
-	void (*fini)(struct nvkm_output *);
+	/* Assembly state. */
+#define NVKM_OUTP_PRIV 1
+#define NVKM_OUTP_USER 2
+	u8 acquired:2;
+	struct nvkm_ior *ior;
 };
 
-void nvkm_output_ctor(const struct nvkm_output_func *, struct nvkm_disp *,
-		      int index, struct dcb_output *, struct nvkm_output *);
-int nvkm_output_new_(const struct nvkm_output_func *, struct nvkm_disp *,
-		     int index, struct dcb_output *, struct nvkm_output **);
-void nvkm_output_del(struct nvkm_output **);
-void nvkm_output_init(struct nvkm_output *);
-void nvkm_output_fini(struct nvkm_output *);
-
-int nv50_dac_output_new(struct nvkm_disp *, int, struct dcb_output *,
-			struct nvkm_output **);
-int nv50_sor_output_new(struct nvkm_disp *, int, struct dcb_output *,
-			struct nvkm_output **);
-int nv50_pior_output_new(struct nvkm_disp *, int, struct dcb_output *,
-			 struct nvkm_output **);
-
-u32 g94_sor_dp_lane_map(struct nvkm_device *, u8 lane);
-
-void gm200_sor_magic(struct nvkm_output *outp);
+int nvkm_outp_ctor(const struct nvkm_outp_func *, struct nvkm_disp *,
+		   int index, struct dcb_output *, struct nvkm_outp *);
+int nvkm_outp_new(struct nvkm_disp *, int index, struct dcb_output *,
+		  struct nvkm_outp **);
+void nvkm_outp_del(struct nvkm_outp **);
+void nvkm_outp_init(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 {
+	void *(*dtor)(struct nvkm_outp *);
+	void (*init)(struct nvkm_outp *);
+	void (*fini)(struct nvkm_outp *);
+	int (*acquire)(struct nvkm_outp *);
+	void (*release)(struct nvkm_outp *, struct nvkm_ior *);
+};
 
 #define OUTP_MSG(o,l,f,a...) do {                                              \
-	struct nvkm_output *_outp = (o);                                       \
+	struct nvkm_outp *_outp = (o);                                         \
 	nvkm_##l(&_outp->disp->engine.subdev, "outp %02x:%04x:%04x: "f"\n",    \
 		 _outp->index, _outp->info.hasht, _outp->info.hashm, ##a);     \
 } while(0)
diff --git a/drivers/gpu/drm/nouveau/nvkm/engine/disp/outpdp.c b/drivers/gpu/drm/nouveau/nvkm/engine/disp/outpdp.c
deleted file mode 100644
index de36f73b14dc..000000000000
--- a/drivers/gpu/drm/nouveau/nvkm/engine/disp/outpdp.c
+++ /dev/null
@@ -1,282 +0,0 @@
-/*
- * 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 "outpdp.h"
-#include "conn.h"
-#include "dport.h"
-#include "priv.h"
-
-#include <subdev/i2c.h>
-
-#include <nvif/event.h>
-
-int
-nvkm_output_dp_train(struct nvkm_output *base, u32 datarate)
-{
-	struct nvkm_output_dp *outp = nvkm_output_dp(base);
-	bool retrain = true;
-	u8 link[2], stat[3];
-	u32 linkrate;
-	int ret, i;
-
-	mutex_lock(&outp->mutex);
-
-	/* check that the link is trained at a high enough rate */
-	ret = nvkm_rdaux(outp->aux, DPCD_LC00_LINK_BW_SET, link, 2);
-	if (ret) {
-		OUTP_DBG(&outp->base,
-			 "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) {
-		OUTP_DBG(&outp->base, "link not trained at sufficient rate");
-		goto done;
-	}
-
-	/* check that link is still trained */
-	ret = nvkm_rdaux(outp->aux, DPCD_LS02, stat, 3);
-	if (ret) {
-		OUTP_DBG(&outp->base,
-			 "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)) {
-				OUTP_DBG(&outp->base,
-					 "lane %d not equalised", lane);
-				goto done;
-			}
-		}
-		retrain = false;
-	} else {
-		OUTP_DBG(&outp->base, "no inter-lane alignment");
-	}
-
-done:
-	if (retrain || !atomic_read(&outp->lt.done)) {
-		/* no sink, but still need to configure source */
-		if (outp->dpcd[DPCD_RC00_DPCD_REV] == 0x00) {
-			outp->dpcd[DPCD_RC01_MAX_LINK_RATE] =
-				outp->base.info.dpconf.link_bw;
-			outp->dpcd[DPCD_RC02] =
-				outp->base.info.dpconf.link_nr;
-		}
-		nvkm_dp_train(outp);
-	}
-
-	mutex_unlock(&outp->mutex);
-	return ret;
-}
-
-static void
-nvkm_output_dp_enable(struct nvkm_output_dp *outp, bool enable)
-{
-	struct nvkm_i2c_aux *aux = outp->aux;
-
-	if (enable) {
-		if (!outp->present) {
-			OUTP_DBG(&outp->base, "aux power -> always");
-			nvkm_i2c_aux_monitor(aux, true);
-			outp->present = true;
-		}
-
-		if (!nvkm_rdaux(aux, DPCD_RC00_DPCD_REV, outp->dpcd,
-				sizeof(outp->dpcd))) {
-			nvkm_output_dp_train(&outp->base, 0);
-			return;
-		}
-	}
-
-	if (outp->present) {
-		OUTP_DBG(&outp->base, "aux power -> demand");
-		nvkm_i2c_aux_monitor(aux, false);
-		outp->present = false;
-	}
-
-	atomic_set(&outp->lt.done, 0);
-}
-
-static int
-nvkm_output_dp_hpd(struct nvkm_notify *notify)
-{
-	const struct nvkm_i2c_ntfy_rep *line = notify->data;
-	struct nvkm_output_dp *outp = container_of(notify, typeof(*outp), hpd);
-	struct nvkm_connector *conn = outp->base.conn;
-	struct nvkm_disp *disp = outp->base.disp;
-	struct nvif_notify_conn_rep_v0 rep = {};
-
-	OUTP_DBG(&outp->base, "HPD: %d", line->mask);
-	nvkm_output_dp_enable(outp, true);
-
-	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
-nvkm_output_dp_irq(struct nvkm_notify *notify)
-{
-	const struct nvkm_i2c_ntfy_rep *line = notify->data;
-	struct nvkm_output_dp *outp = container_of(notify, typeof(*outp), irq);
-	struct nvkm_connector *conn = outp->base.conn;
-	struct nvkm_disp *disp = outp->base.disp;
-	struct nvif_notify_conn_rep_v0 rep = {
-		.mask = NVIF_NOTIFY_CONN_V0_IRQ,
-	};
-
-	OUTP_DBG(&outp->base, "IRQ: %d", line->mask);
-	nvkm_output_dp_train(&outp->base, 0);
-
-	nvkm_event_send(&disp->hpd, rep.mask, conn->index, &rep, sizeof(rep));
-	return NVKM_NOTIFY_KEEP;
-}
-
-static void
-nvkm_output_dp_fini(struct nvkm_output *base)
-{
-	struct nvkm_output_dp *outp = nvkm_output_dp(base);
-	nvkm_notify_put(&outp->hpd);
-	nvkm_notify_put(&outp->irq);
-	nvkm_output_dp_enable(outp, false);
-}
-
-static void
-nvkm_output_dp_init(struct nvkm_output *base)
-{
-	struct nvkm_output_dp *outp = nvkm_output_dp(base);
-	nvkm_notify_put(&outp->base.conn->hpd);
-	nvkm_output_dp_enable(outp, true);
-	nvkm_notify_get(&outp->irq);
-	nvkm_notify_get(&outp->hpd);
-}
-
-static void *
-nvkm_output_dp_dtor(struct nvkm_output *base)
-{
-	struct nvkm_output_dp *outp = nvkm_output_dp(base);
-	nvkm_notify_fini(&outp->hpd);
-	nvkm_notify_fini(&outp->irq);
-	return outp;
-}
-
-static const struct nvkm_output_func
-nvkm_output_dp_func = {
-	.dtor = nvkm_output_dp_dtor,
-	.init = nvkm_output_dp_init,
-	.fini = nvkm_output_dp_fini,
-};
-
-int
-nvkm_output_dp_ctor(const struct nvkm_output_dp_func *func,
-		    struct nvkm_disp *disp, int index, struct dcb_output *dcbE,
-		    struct nvkm_i2c_aux *aux, struct nvkm_output_dp *outp)
-{
-	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;
-
-	nvkm_output_ctor(&nvkm_output_dp_func, disp, index, dcbE, &outp->base);
-	outp->func = func;
-	outp->aux = aux;
-	if (!outp->aux) {
-		OUTP_ERR(&outp->base, "no aux");
-		return -ENODEV;
-	}
-
-	/* bios data is not optional */
-	data = nvbios_dpout_match(bios, outp->base.info.hasht,
-				  outp->base.info.hashm, &outp->version,
-				  &hdr, &cnt, &len, &outp->info);
-	if (!data) {
-		OUTP_ERR(&outp->base, "no bios dp data");
-		return -ENODEV;
-	}
-
-	OUTP_DBG(&outp->base, "bios dp %02x %02x %02x %02x",
-		 outp->version, hdr, cnt, len);
-
-	/* link maintenance */
-	ret = nvkm_notify_init(NULL, &i2c->event, nvkm_output_dp_irq, true,
-			       &(struct nvkm_i2c_ntfy_req) {
-				.mask = NVKM_I2C_IRQ,
-				.port = outp->aux->id,
-			       },
-			       sizeof(struct nvkm_i2c_ntfy_req),
-			       sizeof(struct nvkm_i2c_ntfy_rep),
-			       &outp->irq);
-	if (ret) {
-		OUTP_ERR(&outp->base, "error monitoring aux irq: %d", ret);
-		return ret;
-	}
-
-	mutex_init(&outp->mutex);
-	atomic_set(&outp->lt.done, 0);
-
-	/* hotplug detect, replaces gpio-based mechanism with aux events */
-	ret = nvkm_notify_init(NULL, &i2c->event, nvkm_output_dp_hpd, true,
-			       &(struct nvkm_i2c_ntfy_req) {
-				.mask = NVKM_I2C_PLUG | NVKM_I2C_UNPLUG,
-				.port = outp->aux->id,
-			       },
-			       sizeof(struct nvkm_i2c_ntfy_req),
-			       sizeof(struct nvkm_i2c_ntfy_rep),
-			       &outp->hpd);
-	if (ret) {
-		OUTP_ERR(&outp->base, "error monitoring aux hpd: %d", ret);
-		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,
-		    struct nvkm_output **poutp)
-{
-	struct nvkm_i2c *i2c = disp->engine.subdev.device->i2c;
-	struct nvkm_i2c_aux *aux = nvkm_i2c_aux_find(i2c, dcbE->i2c_index);
-	struct nvkm_output_dp *outp;
-
-	if (!(outp = kzalloc(sizeof(*outp), GFP_KERNEL)))
-		return -ENOMEM;
-	*poutp = &outp->base;
-
-	return nvkm_output_dp_ctor(func, disp, index, dcbE, aux, outp);
-}
diff --git a/drivers/gpu/drm/nouveau/nvkm/engine/disp/outpdp.h b/drivers/gpu/drm/nouveau/nvkm/engine/disp/outpdp.h
deleted file mode 100644
index 3c83a561cd88..000000000000
--- a/drivers/gpu/drm/nouveau/nvkm/engine/disp/outpdp.h
+++ /dev/null
@@ -1,76 +0,0 @@
-#ifndef __NVKM_DISP_OUTP_DP_H__
-#define __NVKM_DISP_OUTP_DP_H__
-#define nvkm_output_dp(p) container_of((p), struct nvkm_output_dp, base)
-#ifndef MSG
-#define MSG(l,f,a...)                                                          \
-	nvkm_##l(&outp->base.disp->engine.subdev, "%02x:%04x:%04x: "f,         \
-		 outp->base.index, outp->base.info.hasht,                      \
-		 outp->base.info.hashm, ##a)
-#define DBG(f,a...) MSG(debug, f, ##a)
-#define ERR(f,a...) MSG(error, f, ##a)
-#endif
-#include "outp.h"
-
-#include <core/notify.h>
-#include <subdev/bios.h>
-#include <subdev/bios/dp.h>
-
-struct nvkm_output_dp {
-	const struct nvkm_output_dp_func *func;
-	struct nvkm_output base;
-
-	struct nvbios_dpout info;
-	u8 version;
-
-	struct nvkm_i2c_aux *aux;
-
-	struct nvkm_notify irq;
-	struct nvkm_notify hpd;
-	bool present;
-	u8 dpcd[16];
-
-	struct mutex mutex;
-	struct {
-		atomic_t done;
-		bool mst;
-	} lt;
-};
-
-struct nvkm_output_dp_func {
-	int (*pattern)(struct nvkm_output_dp *, int);
-	int (*lnk_pwr)(struct nvkm_output_dp *, int nr);
-	int (*lnk_ctl)(struct nvkm_output_dp *, int nr, int bw, bool ef);
-	int (*drv_ctl)(struct nvkm_output_dp *, int ln, int vs, int pe, int pc);
-	void (*vcpi)(struct nvkm_output_dp *, int head, u8 start_slot,
-		     u8 num_slots, u16 pbn, u16 aligned_pbn);
-};
-
-int nvkm_output_dp_train(struct nvkm_output *, u32 rate);
-
-int nvkm_output_dp_ctor(const struct nvkm_output_dp_func *, struct nvkm_disp *,
-			int index, struct dcb_output *, struct nvkm_i2c_aux *,
-			struct nvkm_output_dp *);
-int nvkm_output_dp_new_(const struct nvkm_output_dp_func *, struct nvkm_disp *,
-			int index, struct dcb_output *,
-			struct nvkm_output **);
-
-int nv50_pior_dp_new(struct nvkm_disp *, int, struct dcb_output *,
-		     struct nvkm_output **);
-
-int g94_sor_dp_new(struct nvkm_disp *, int, struct dcb_output *,
-		   struct nvkm_output **);
-int g94_sor_dp_lnk_pwr(struct nvkm_output_dp *, int);
-
-int gf119_sor_dp_new(struct nvkm_disp *, int, struct dcb_output *,
-		     struct nvkm_output **);
-int gf119_sor_dp_lnk_ctl(struct nvkm_output_dp *, int, int, bool);
-int gf119_sor_dp_drv_ctl(struct nvkm_output_dp *, int, int, int, int);
-void gf119_sor_dp_vcpi(struct nvkm_output_dp *, int, u8, u8, u16, u16);
-
-int gm107_sor_dp_new(struct nvkm_disp *, int, struct dcb_output *,
-		     struct nvkm_output **);
-int gm107_sor_dp_pattern(struct nvkm_output_dp *, int);
-
-int gm200_sor_dp_new(struct nvkm_disp *, int, struct dcb_output *,
-		     struct nvkm_output **);
-#endif
diff --git a/drivers/gpu/drm/nouveau/nvkm/engine/disp/ovlynv50.c b/drivers/gpu/drm/nouveau/nvkm/engine/disp/ovlynv50.c
index 2a49c46425cd..9ebaaa6e9e33 100644
--- a/drivers/gpu/drm/nouveau/nvkm/engine/disp/ovlynv50.c
+++ b/drivers/gpu/drm/nouveau/nvkm/engine/disp/ovlynv50.c
@@ -22,6 +22,7 @@
  * Authors: Ben Skeggs
  */
 #include "dmacnv50.h"
+#include "head.h"
 #include "rootnv50.h"
 
 #include <core/client.h>
@@ -50,7 +51,7 @@ nv50_disp_ovly_new(const struct nv50_disp_dmac_func *func,
 		nvif_ioctl(parent, "create disp overlay channel dma vers %d "
 				   "pushbuf %016llx head %d\n",
 			   args->v0.version, args->v0.pushbuf, args->v0.head);
-		if (args->v0.head > disp->base.head.nr)
+		if (!nvkm_head_find(&disp->base, args->v0.head))
 			return -EINVAL;
 		push = args->v0.pushbuf;
 		head = args->v0.head;
diff --git a/drivers/gpu/drm/nouveau/nvkm/engine/disp/piornv50.c b/drivers/gpu/drm/nouveau/nvkm/engine/disp/piornv50.c
index 6c532eadba17..99b3b9050635 100644
--- a/drivers/gpu/drm/nouveau/nvkm/engine/disp/piornv50.c
+++ b/drivers/gpu/drm/nouveau/nvkm/engine/disp/piornv50.c
@@ -21,111 +21,114 @@
  *
  * Authors: Ben Skeggs
  */
-#include "outpdp.h"
-#include "nv50.h"
+#include "ior.h"
+#include "head.h"
 
-#include <core/client.h>
 #include <subdev/i2c.h>
 #include <subdev/timer.h>
 
-#include <nvif/cl5070.h>
-#include <nvif/unpack.h>
-
-int
-nv50_pior_power(NV50_DISP_MTHD_V1)
+static void
+nv50_pior_clock(struct nvkm_ior *pior)
 {
-	struct nvkm_device *device = disp->base.engine.subdev.device;
-	const u32 soff = outp->or * 0x800;
-	union {
-		struct nv50_disp_pior_pwr_v0 v0;
-	} *args = data;
-	u32 ctrl, type;
-	int ret = -ENOSYS;
+	struct nvkm_device *device = pior->disp->engine.subdev.device;
+	const u32 poff = nv50_ior_base(pior);
+	nvkm_mask(device, 0x614380 + poff, 0x00000707, 0x00000001);
+}
 
-	nvif_ioctl(object, "disp pior pwr size %d\n", size);
-	if (!(ret = nvif_unpack(ret, &data, &size, args->v0, 0, 0, false))) {
-		nvif_ioctl(object, "disp pior pwr vers %d state %d type %x\n",
-			   args->v0.version, args->v0.state, args->v0.type);
-		if (args->v0.type > 0x0f)
-			return -EINVAL;
-		ctrl = !!args->v0.state;
-		type = args->v0.type;
-	} else
+static int
+nv50_pior_dp_links(struct nvkm_ior *pior, struct nvkm_i2c_aux *aux)
+{
+	int ret = nvkm_i2c_aux_lnk_ctl(aux, pior->dp.nr, pior->dp.bw,
+					    pior->dp.ef);
+	if (ret)
 		return ret;
+	return 1;
+}
 
+static void
+nv50_pior_power_wait(struct nvkm_device *device, u32 poff)
+{
 	nvkm_msec(device, 2000,
-		if (!(nvkm_rd32(device, 0x61e004 + soff) & 0x80000000))
-			break;
-	);
-	nvkm_mask(device, 0x61e004 + soff, 0x80000101, 0x80000000 | ctrl);
-	nvkm_msec(device, 2000,
-		if (!(nvkm_rd32(device, 0x61e004 + soff) & 0x80000000))
+		if (!(nvkm_rd32(device, 0x61e004 + poff) & 0x80000000))
 			break;
 	);
-	disp->pior.type[outp->or] = type;
-	return 0;
 }
 
-/******************************************************************************
- * TMDS
- *****************************************************************************/
-static const struct nvkm_output_func
-nv50_pior_output_func = {
-};
-
-int
-nv50_pior_output_new(struct nvkm_disp *disp, int index,
-		     struct dcb_output *dcbE, struct nvkm_output **poutp)
+static void
+nv50_pior_power(struct nvkm_ior *pior, bool normal, bool pu,
+	       bool data, bool vsync, bool hsync)
 {
-	return nvkm_output_new_(&nv50_pior_output_func, disp,
-				index, dcbE, poutp);
-}
+	struct nvkm_device *device = pior->disp->engine.subdev.device;
+	const u32  poff = nv50_ior_base(pior);
+	const u32 shift = normal ? 0 : 16;
+	const u32 state = 0x80000000 | (0x00000001 * !!pu) << shift;
+	const u32 field = 0x80000000 | (0x00000101 << shift);
 
-/******************************************************************************
- * DisplayPort
- *****************************************************************************/
-static int
-nv50_pior_output_dp_pattern(struct nvkm_output_dp *outp, int pattern)
-{
-	return 0;
+	nv50_pior_power_wait(device, poff);
+	nvkm_mask(device, 0x61e004 + poff, field, state);
+	nv50_pior_power_wait(device, poff);
 }
 
-static int
-nv50_pior_output_dp_lnk_pwr(struct nvkm_output_dp *outp, int nr)
+void
+nv50_pior_depth(struct nvkm_ior *ior, struct nvkm_ior_state *state, u32 ctrl)
 {
-	return 0;
+	/* GF119 moves this information to per-head methods, which is
+	 * a lot more convenient, and where our shared code expect it.
+	 */
+	if (state->head && state == &ior->asy) {
+		struct nvkm_head *head =
+			nvkm_head_find(ior->disp, __ffs(state->head));
+		if (!WARN_ON(!head)) {
+			struct nvkm_head_state *state = &head->asy;
+			switch ((ctrl & 0x000f0000) >> 16) {
+			case 6: state->or.depth = 30; break;
+			case 5: state->or.depth = 24; break;
+			case 2: state->or.depth = 18; break;
+			case 0: state->or.depth = 18; break; /*XXX*/
+			default:
+				state->or.depth = 18;
+				WARN_ON(1);
+				break;
+			}
+		}
+	}
 }
 
-static int
-nv50_pior_output_dp_lnk_ctl(struct nvkm_output_dp *outp,
-			    int nr, int bw, bool ef)
+static void
+nv50_pior_state(struct nvkm_ior *pior, struct nvkm_ior_state *state)
 {
-	int ret = nvkm_i2c_aux_lnk_ctl(outp->aux, nr, bw, ef);
-	if (ret)
-		return ret;
-	return 1;
+	struct nvkm_device *device = pior->disp->engine.subdev.device;
+	const u32 coff = pior->id * 8 + (state == &pior->arm) * 4;
+	u32 ctrl = nvkm_rd32(device, 0x610b80 + coff);
+
+	state->proto_evo = (ctrl & 0x00000f00) >> 8;
+	state->rgdiv = 1;
+	switch (state->proto_evo) {
+	case 0: state->proto = TMDS; break;
+	default:
+		state->proto = UNKNOWN;
+		break;
+	}
+
+	state->head = ctrl & 0x00000003;
+	nv50_pior_depth(pior, state, ctrl);
 }
 
-static const struct nvkm_output_dp_func
-nv50_pior_output_dp_func = {
-	.pattern = nv50_pior_output_dp_pattern,
-	.lnk_pwr = nv50_pior_output_dp_lnk_pwr,
-	.lnk_ctl = nv50_pior_output_dp_lnk_ctl,
+static const struct nvkm_ior_func
+nv50_pior = {
+	.state = nv50_pior_state,
+	.power = nv50_pior_power,
+	.clock = nv50_pior_clock,
+	.dp = {
+		.links = nv50_pior_dp_links,
+	},
 };
 
 int
-nv50_pior_dp_new(struct nvkm_disp *disp, int index, struct dcb_output *dcbE,
-		 struct nvkm_output **poutp)
+nv50_pior_new(struct nvkm_disp *disp, int id)
 {
-	struct nvkm_i2c *i2c = disp->engine.subdev.device->i2c;
-	struct nvkm_i2c_aux *aux =
-		nvkm_i2c_aux_find(i2c, NVKM_I2C_AUX_EXT(dcbE->extdev));
-	struct nvkm_output_dp *outp;
-
-	if (!(outp = kzalloc(sizeof(*outp), GFP_KERNEL)))
-		return -ENOMEM;
-	*poutp = &outp->base;
-
-	return nvkm_output_dp_ctor(&nv50_pior_output_dp_func, disp,
-				   index, dcbE, aux, outp);
+	struct nvkm_device *device = disp->engine.subdev.device;
+	if (!(nvkm_rd32(device, 0x610184) & (0x10000000 << id)))
+		return 0;
+	return nvkm_ior_new_(&nv50_pior, disp, PIOR, id);
 }
diff --git a/drivers/gpu/drm/nouveau/nvkm/engine/disp/priv.h b/drivers/gpu/drm/nouveau/nvkm/engine/disp/priv.h
index c2452957fc57..5772f0094129 100644
--- a/drivers/gpu/drm/nouveau/nvkm/engine/disp/priv.h
+++ b/drivers/gpu/drm/nouveau/nvkm/engine/disp/priv.h
@@ -2,42 +2,18 @@
 #define __NVKM_DISP_PRIV_H__
 #include <engine/disp.h>
 #include "outp.h"
-#include "outpdp.h"
 
 int nvkm_disp_ctor(const struct nvkm_disp_func *, struct nvkm_device *,
-		   int index, int heads, struct nvkm_disp *);
+		   int index, struct nvkm_disp *);
 int nvkm_disp_new_(const struct nvkm_disp_func *, struct nvkm_device *,
-		   int index, int heads, struct nvkm_disp **);
+		   int index, struct nvkm_disp **);
 void nvkm_disp_vblank(struct nvkm_disp *, int head);
 
-struct nvkm_disp_func_outp {
-	int (* crt)(struct nvkm_disp *, int index, struct dcb_output *,
-		    struct nvkm_output **);
-	int (*  tv)(struct nvkm_disp *, int index, struct dcb_output *,
-		    struct nvkm_output **);
-	int (*tmds)(struct nvkm_disp *, int index, struct dcb_output *,
-		    struct nvkm_output **);
-	int (*lvds)(struct nvkm_disp *, int index, struct dcb_output *,
-		    struct nvkm_output **);
-	int (*  dp)(struct nvkm_disp *, int index, struct dcb_output *,
-		    struct nvkm_output **);
-};
-
 struct nvkm_disp_func {
 	void *(*dtor)(struct nvkm_disp *);
 	void (*intr)(struct nvkm_disp *);
 
 	const struct nvkm_disp_oclass *(*root)(struct nvkm_disp *);
-
-	struct {
-		void (*vblank_init)(struct nvkm_disp *, int head);
-		void (*vblank_fini)(struct nvkm_disp *, int head);
-	} head;
-
-	struct {
-		const struct nvkm_disp_func_outp internal;
-		const struct nvkm_disp_func_outp external;
-	} outp;
 };
 
 int  nvkm_disp_ntfy(struct nvkm_object *, u32, struct nvkm_event **);
diff --git a/drivers/gpu/drm/nouveau/nvkm/engine/disp/rootgf119.c b/drivers/gpu/drm/nouveau/nvkm/engine/disp/rootgf119.c
index 335d88823c22..333c8424b413 100644
--- a/drivers/gpu/drm/nouveau/nvkm/engine/disp/rootgf119.c
+++ b/drivers/gpu/drm/nouveau/nvkm/engine/disp/rootgf119.c
@@ -22,49 +22,13 @@
  * Authors: Ben Skeggs
  */
 #include "rootnv50.h"
+#include "head.h"
 #include "dmacnv50.h"
 
-#include <core/client.h>
 #include <core/ramht.h>
 #include <subdev/timer.h>
 
 #include <nvif/class.h>
-#include <nvif/cl5070.h>
-#include <nvif/unpack.h>
-
-int
-gf119_disp_root_scanoutpos(NV50_DISP_MTHD_V0)
-{
-	struct nvkm_device *device = disp->base.engine.subdev.device;
-	const u32 total  = nvkm_rd32(device, 0x640414 + (head * 0x300));
-	const u32 blanke = nvkm_rd32(device, 0x64041c + (head * 0x300));
-	const u32 blanks = nvkm_rd32(device, 0x640420 + (head * 0x300));
-	union {
-		struct nv50_disp_scanoutpos_v0 v0;
-	} *args = data;
-	int ret = -ENOSYS;
-
-	nvif_ioctl(object, "disp scanoutpos size %d\n", size);
-	if (!(ret = nvif_unpack(ret, &data, &size, args->v0, 0, 0, false))) {
-		nvif_ioctl(object, "disp scanoutpos vers %d\n",
-			   args->v0.version);
-		args->v0.vblanke = (blanke & 0xffff0000) >> 16;
-		args->v0.hblanke = (blanke & 0x0000ffff);
-		args->v0.vblanks = (blanks & 0xffff0000) >> 16;
-		args->v0.hblanks = (blanks & 0x0000ffff);
-		args->v0.vtotal  = ( total & 0xffff0000) >> 16;
-		args->v0.htotal  = ( total & 0x0000ffff);
-		args->v0.time[0] = ktime_to_ns(ktime_get());
-		args->v0.vline = /* vline read locks hline */
-			nvkm_rd32(device, 0x616340 + (head * 0x800)) & 0xffff;
-		args->v0.time[1] = ktime_to_ns(ktime_get());
-		args->v0.hline =
-			nvkm_rd32(device, 0x616344 + (head * 0x800)) & 0xffff;
-	} else
-		return ret;
-
-	return 0;
-}
 
 void
 gf119_disp_root_fini(struct nv50_disp_root *root)
@@ -78,6 +42,7 @@ int
 gf119_disp_root_init(struct nv50_disp_root *root)
 {
 	struct nv50_disp *disp = root->disp;
+	struct nvkm_head *head;
 	struct nvkm_device *device = disp->base.engine.subdev.device;
 	u32 tmp;
 	int i;
@@ -88,13 +53,14 @@ gf119_disp_root_init(struct nv50_disp_root *root)
 	 */
 
 	/* ... CRTC caps */
-	for (i = 0; i < disp->base.head.nr; i++) {
-		tmp = nvkm_rd32(device, 0x616104 + (i * 0x800));
-		nvkm_wr32(device, 0x6101b4 + (i * 0x800), tmp);
-		tmp = nvkm_rd32(device, 0x616108 + (i * 0x800));
-		nvkm_wr32(device, 0x6101b8 + (i * 0x800), tmp);
-		tmp = nvkm_rd32(device, 0x61610c + (i * 0x800));
-		nvkm_wr32(device, 0x6101bc + (i * 0x800), tmp);
+	list_for_each_entry(head, &disp->base.head, head) {
+		const u32 hoff = head->id * 0x800;
+		tmp = nvkm_rd32(device, 0x616104 + hoff);
+		nvkm_wr32(device, 0x6101b4 + hoff, tmp);
+		tmp = nvkm_rd32(device, 0x616108 + hoff);
+		nvkm_wr32(device, 0x6101b8 + hoff, tmp);
+		tmp = nvkm_rd32(device, 0x61610c + hoff);
+		nvkm_wr32(device, 0x6101bc + hoff, tmp);
 	}
 
 	/* ... DAC caps */
@@ -134,8 +100,10 @@ gf119_disp_root_init(struct nv50_disp_root *root)
 	 *
 	 * ftp://download.nvidia.com/open-gpu-doc/gk104-disable-underflow-reporting/1/gk104-disable-underflow-reporting.txt
 	 */
-	for (i = 0; i < disp->base.head.nr; i++)
-		nvkm_mask(device, 0x616308 + (i * 0x800), 0x00000111, 0x00000010);
+	list_for_each_entry(head, &disp->base.head, head) {
+		const u32 hoff = head->id * 0x800;
+		nvkm_mask(device, 0x616308 + hoff, 0x00000111, 0x00000010);
+	}
 
 	return 0;
 }
diff --git a/drivers/gpu/drm/nouveau/nvkm/engine/disp/rootnv04.c b/drivers/gpu/drm/nouveau/nvkm/engine/disp/rootnv04.c
index f535f43231e2..7f3e2554a83d 100644
--- a/drivers/gpu/drm/nouveau/nvkm/engine/disp/rootnv04.c
+++ b/drivers/gpu/drm/nouveau/nvkm/engine/disp/rootnv04.c
@@ -23,6 +23,7 @@
  */
 #define nv04_disp_root(p) container_of((p), struct nv04_disp_root, object)
 #include "priv.h"
+#include "head.h"
 
 #include <core/client.h>
 
@@ -36,73 +37,30 @@ struct nv04_disp_root {
 };
 
 static int
-nv04_disp_scanoutpos(struct nv04_disp_root *root,
-		     void *data, u32 size, int head)
-{
-	struct nvkm_device *device = root->disp->engine.subdev.device;
-	struct nvkm_object *object = &root->object;
-	const u32 hoff = head * 0x2000;
-	union {
-		struct nv04_disp_scanoutpos_v0 v0;
-	} *args = data;
-	u32 line;
-	int ret = -ENOSYS;
-
-	nvif_ioctl(object, "disp scanoutpos size %d\n", size);
-	if (!(ret = nvif_unpack(ret, &data, &size, args->v0, 0, 0, false))) {
-		nvif_ioctl(object, "disp scanoutpos vers %d\n",
-			   args->v0.version);
-		args->v0.vblanks = nvkm_rd32(device, 0x680800 + hoff) & 0xffff;
-		args->v0.vtotal  = nvkm_rd32(device, 0x680804 + hoff) & 0xffff;
-		args->v0.vblanke = args->v0.vtotal - 1;
-
-		args->v0.hblanks = nvkm_rd32(device, 0x680820 + hoff) & 0xffff;
-		args->v0.htotal  = nvkm_rd32(device, 0x680824 + hoff) & 0xffff;
-		args->v0.hblanke = args->v0.htotal - 1;
-
-		/*
-		 * If output is vga instead of digital then vtotal/htotal is
-		 * invalid so we have to give up and trigger the timestamping
-		 * fallback in the drm core.
-		 */
-		if (!args->v0.vtotal || !args->v0.htotal)
-			return -ENOTSUPP;
-
-		args->v0.time[0] = ktime_to_ns(ktime_get());
-		line = nvkm_rd32(device, 0x600868 + hoff);
-		args->v0.time[1] = ktime_to_ns(ktime_get());
-		args->v0.hline = (line & 0xffff0000) >> 16;
-		args->v0.vline = (line & 0x0000ffff);
-	} else
-		return ret;
-
-	return 0;
-}
-
-static int
 nv04_disp_mthd(struct nvkm_object *object, u32 mthd, void *data, u32 size)
 {
 	struct nv04_disp_root *root = nv04_disp_root(object);
 	union {
 		struct nv04_disp_mthd_v0 v0;
 	} *args = data;
-	int head, ret = -ENOSYS;
+	struct nvkm_head *head;
+	int id, ret = -ENOSYS;
 
 	nvif_ioctl(object, "disp mthd size %d\n", size);
 	if (!(ret = nvif_unpack(ret, &data, &size, args->v0, 0, 0, true))) {
 		nvif_ioctl(object, "disp mthd vers %d mthd %02x head %d\n",
 			   args->v0.version, args->v0.method, args->v0.head);
 		mthd = args->v0.method;
-		head = args->v0.head;
+		id   = args->v0.head;
 	} else
 		return ret;
 
-	if (head < 0 || head >= 2)
+	if (!(head = nvkm_head_find(root->disp, id)))
 		return -ENXIO;
 
 	switch (mthd) {
 	case NV04_DISP_SCANOUTPOS:
-		return nv04_disp_scanoutpos(root, data, size, head);
+		return nvkm_head_mthd_scanoutpos(object, head, data, size);
 	default:
 		break;
 	}
diff --git a/drivers/gpu/drm/nouveau/nvkm/engine/disp/rootnv50.c b/drivers/gpu/drm/nouveau/nvkm/engine/disp/rootnv50.c
index e70dc6a9ff7d..1208524aae14 100644
--- a/drivers/gpu/drm/nouveau/nvkm/engine/disp/rootnv50.c
+++ b/drivers/gpu/drm/nouveau/nvkm/engine/disp/rootnv50.c
@@ -23,6 +23,9 @@
  */
 #include "rootnv50.h"
 #include "dmacnv50.h"
+#include "dp.h"
+#include "head.h"
+#include "ior.h"
 
 #include <core/client.h>
 #include <core/ramht.h>
@@ -32,40 +35,6 @@
 #include <nvif/cl5070.h>
 #include <nvif/unpack.h>
 
-int
-nv50_disp_root_scanoutpos(NV50_DISP_MTHD_V0)
-{
-	struct nvkm_device *device = disp->base.engine.subdev.device;
-	const u32 blanke = nvkm_rd32(device, 0x610aec + (head * 0x540));
-	const u32 blanks = nvkm_rd32(device, 0x610af4 + (head * 0x540));
-	const u32 total  = nvkm_rd32(device, 0x610afc + (head * 0x540));
-	union {
-		struct nv50_disp_scanoutpos_v0 v0;
-	} *args = data;
-	int ret = -ENOSYS;
-
-	nvif_ioctl(object, "disp scanoutpos size %d\n", size);
-	if (!(ret = nvif_unpack(ret, &data, &size, args->v0, 0, 0, false))) {
-		nvif_ioctl(object, "disp scanoutpos vers %d\n",
-			   args->v0.version);
-		args->v0.vblanke = (blanke & 0xffff0000) >> 16;
-		args->v0.hblanke = (blanke & 0x0000ffff);
-		args->v0.vblanks = (blanks & 0xffff0000) >> 16;
-		args->v0.hblanks = (blanks & 0x0000ffff);
-		args->v0.vtotal  = ( total & 0xffff0000) >> 16;
-		args->v0.htotal  = ( total & 0x0000ffff);
-		args->v0.time[0] = ktime_to_ns(ktime_get());
-		args->v0.vline = /* vline read locks hline */
-			nvkm_rd32(device, 0x616340 + (head * 0x800)) & 0xffff;
-		args->v0.time[1] = ktime_to_ns(ktime_get());
-		args->v0.hline =
-			nvkm_rd32(device, 0x616344 + (head * 0x800)) & 0xffff;
-	} else
-		return ret;
-
-	return 0;
-}
-
 static int
 nv50_disp_root_mthd_(struct nvkm_object *object, u32 mthd, void *data, u32 size)
 {
@@ -75,11 +44,10 @@ nv50_disp_root_mthd_(struct nvkm_object *object, u32 mthd, void *data, u32 size)
 	} *args = data;
 	struct nv50_disp_root *root = nv50_disp_root(object);
 	struct nv50_disp *disp = root->disp;
-	const struct nv50_disp_func *func = disp->func;
-	struct nvkm_output *outp = NULL;
-	struct nvkm_output *temp;
+	struct nvkm_outp *temp, *outp = NULL;
+	struct nvkm_head *head;
 	u16 type, mask = 0;
-	int head, ret = -ENOSYS;
+	int hidx, ret = -ENOSYS;
 
 	if (mthd != NV50_DISP_MTHD)
 		return -EINVAL;
@@ -89,7 +57,7 @@ nv50_disp_root_mthd_(struct nvkm_object *object, u32 mthd, void *data, u32 size)
 		nvif_ioctl(object, "disp mthd vers %d mthd %02x head %d\n",
 			   args->v0.version, args->v0.method, args->v0.head);
 		mthd = args->v0.method;
-		head = args->v0.head;
+		hidx = args->v0.head;
 	} else
 	if (!(ret = nvif_unpack(ret, &data, &size, args->v1, 1, 1, true))) {
 		nvif_ioctl(object, "disp mthd vers %d mthd %02x "
@@ -99,11 +67,11 @@ nv50_disp_root_mthd_(struct nvkm_object *object, u32 mthd, void *data, u32 size)
 		mthd = args->v1.method;
 		type = args->v1.hasht;
 		mask = args->v1.hashm;
-		head = ffs((mask >> 8) & 0x0f) - 1;
+		hidx = ffs((mask >> 8) & 0x0f) - 1;
 	} else
 		return ret;
 
-	if (head < 0 || head >= disp->base.head.nr)
+	if (!(head = nvkm_head_find(&disp->base, hidx)))
 		return -ENXIO;
 
 	if (mask) {
@@ -119,27 +87,126 @@ nv50_disp_root_mthd_(struct nvkm_object *object, u32 mthd, void *data, u32 size)
 	}
 
 	switch (mthd) {
-	case NV50_DISP_SCANOUTPOS:
-		return func->head.scanoutpos(object, disp, data, size, head);
+	case NV50_DISP_SCANOUTPOS: {
+		return nvkm_head_mthd_scanoutpos(object, head, data, size);
+	}
 	default:
 		break;
 	}
 
 	switch (mthd * !!outp) {
-	case NV50_DISP_MTHD_V1_DAC_PWR:
-		return func->dac.power(object, disp, data, size, head, outp);
-	case NV50_DISP_MTHD_V1_DAC_LOAD:
-		return func->dac.sense(object, disp, data, size, head, outp);
-	case NV50_DISP_MTHD_V1_SOR_PWR:
-		return func->sor.power(object, disp, data, size, head, outp);
-	case NV50_DISP_MTHD_V1_SOR_HDA_ELD:
-		if (!func->sor.hda_eld)
+	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: {
+		union {
+			struct nv50_disp_dac_load_v0 v0;
+		} *args = data;
+		int ret = -ENOSYS;
+		if (!(ret = nvif_unpack(ret, &data, &size, args->v0, 0, 0, false))) {
+			if (args->v0.data & 0xfff00000)
+				return -EINVAL;
+			ret = nvkm_outp_acquire(outp, NVKM_OUTP_PRIV);
+			if (ret)
+				return ret;
+			ret = outp->ior->func->sense(outp->ior, args->v0.data);
+			nvkm_outp_release(outp, NVKM_OUTP_PRIV);
+			if (ret < 0)
+				return ret;
+			args->v0.load = ret;
+			return 0;
+		} else
+			return ret;
+	}
+		break;
+	case NV50_DISP_MTHD_V1_SOR_HDA_ELD: {
+		union {
+			struct nv50_disp_sor_hda_eld_v0 v0;
+		} *args = data;
+		struct nvkm_ior *ior = outp->ior;
+		int ret = -ENOSYS;
+
+		nvif_ioctl(object, "disp sor hda eld size %d\n", size);
+		if (!(ret = nvif_unpack(ret, &data, &size, args->v0, 0, 0, true))) {
+			nvif_ioctl(object, "disp sor hda eld vers %d\n",
+				   args->v0.version);
+			if (size > 0x60)
+				return -E2BIG;
+		} else
+			return ret;
+
+		if (!ior->func->hda.hpd)
 			return -ENODEV;
-		return func->sor.hda_eld(object, disp, data, size, head, outp);
-	case NV50_DISP_MTHD_V1_SOR_HDMI_PWR:
-		if (!func->sor.hdmi)
+
+		if (size && args->v0.data[0]) {
+			if (outp->info.type == DCB_OUTPUT_DP)
+				ior->func->dp.audio(ior, hidx, true);
+			ior->func->hda.hpd(ior, hidx, true);
+			ior->func->hda.eld(ior, data, size);
+		} else {
+			if (outp->info.type == DCB_OUTPUT_DP)
+				ior->func->dp.audio(ior, hidx, false);
+			ior->func->hda.hpd(ior, hidx, false);
+		}
+
+		return 0;
+	}
+		break;
+	case NV50_DISP_MTHD_V1_SOR_HDMI_PWR: {
+		union {
+			struct nv50_disp_sor_hdmi_pwr_v0 v0;
+		} *args = data;
+		u8 *vendor, vendor_size;
+		u8 *avi, avi_size;
+		int ret = -ENOSYS;
+
+		nvif_ioctl(object, "disp sor hdmi ctrl size %d\n", size);
+		if (!(ret = nvif_unpack(ret, &data, &size, args->v0, 0, 0, true))) {
+			nvif_ioctl(object, "disp sor hdmi ctrl vers %d state %d "
+					   "max_ac_packet %d rekey %d\n",
+				   args->v0.version, args->v0.state,
+				   args->v0.max_ac_packet, args->v0.rekey);
+			if (args->v0.max_ac_packet > 0x1f || args->v0.rekey > 0x7f)
+				return -EINVAL;
+			if ((args->v0.avi_infoframe_length
+			     + args->v0.vendor_infoframe_length) > size)
+				return -EINVAL;
+			else
+			if ((args->v0.avi_infoframe_length
+			     + args->v0.vendor_infoframe_length) < size)
+				return -E2BIG;
+			avi = data;
+			avi_size = args->v0.avi_infoframe_length;
+			vendor = avi + avi_size;
+			vendor_size = args->v0.vendor_infoframe_length;
+		} else
+			return ret;
+
+		if (!outp->ior->func->hdmi.ctrl)
 			return -ENODEV;
-		return func->sor.hdmi(object, disp, data, size, head, outp);
+
+		outp->ior->func->hdmi.ctrl(outp->ior, hidx, args->v0.state,
+					   args->v0.max_ac_packet,
+					   args->v0.rekey, avi, avi_size,
+					   vendor, vendor_size);
+		return 0;
+	}
+		break;
 	case NV50_DISP_MTHD_V1_SOR_LVDS_SCRIPT: {
 		union {
 			struct nv50_disp_sor_lvds_script_v0 v0;
@@ -156,32 +223,8 @@ nv50_disp_root_mthd_(struct nvkm_object *object, u32 mthd, void *data, u32 size)
 			return ret;
 	}
 		break;
-	case NV50_DISP_MTHD_V1_SOR_DP_PWR: {
-		struct nvkm_output_dp *outpdp = nvkm_output_dp(outp);
-		union {
-			struct nv50_disp_sor_dp_pwr_v0 v0;
-		} *args = data;
-		int ret = -ENOSYS;
-		nvif_ioctl(object, "disp sor dp pwr size %d\n", size);
-		if (!(ret = nvif_unpack(ret, &data, &size, args->v0, 0, 0, false))) {
-			nvif_ioctl(object, "disp sor dp pwr vers %d state %d\n",
-				   args->v0.version, args->v0.state);
-			if (args->v0.state == 0) {
-				nvkm_notify_put(&outpdp->irq);
-				outpdp->func->lnk_pwr(outpdp, 0);
-				atomic_set(&outpdp->lt.done, 0);
-				return 0;
-			} else
-			if (args->v0.state != 0) {
-				nvkm_output_dp_train(&outpdp->base, 0);
-				return 0;
-			}
-		} else
-			return ret;
-	}
-		break;
 	case NV50_DISP_MTHD_V1_SOR_DP_MST_LINK: {
-		struct nvkm_output_dp *outpdp = nvkm_output_dp(outp);
+		struct nvkm_dp *dp = nvkm_dp(outp);
 		union {
 			struct nv50_disp_sor_dp_mst_link_v0 v0;
 		} *args = data;
@@ -190,18 +233,13 @@ 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))) {
 			nvif_ioctl(object, "disp sor dp mst link vers %d state %d\n",
 				   args->v0.version, args->v0.state);
-			if (outpdp->lt.mst != !!args->v0.state) {
-				outpdp->lt.mst = !!args->v0.state;
-				atomic_set(&outpdp->lt.done, 0);
-				nvkm_output_dp_train(&outpdp->base, 0);
-			}
+			dp->lt.mst = !!args->v0.state;
 			return 0;
 		} else
 			return ret;
 	}
 		break;
 	case NV50_DISP_MTHD_V1_SOR_DP_MST_VCPI: {
-		struct nvkm_output_dp *outpdp = nvkm_output_dp(outp);
 		union {
 			struct nv50_disp_sor_dp_mst_vcpi_v0 v0;
 		} *args = data;
@@ -213,20 +251,18 @@ nv50_disp_root_mthd_(struct nvkm_object *object, u32 mthd, void *data, u32 size)
 				   args->v0.version, args->v0.start_slot,
 				   args->v0.num_slots, args->v0.pbn,
 				   args->v0.aligned_pbn);
-			if (!outpdp->func->vcpi)
+			if (!outp->ior->func->dp.vcpi)
 				return -ENODEV;
-			outpdp->func->vcpi(outpdp, head, args->v0.start_slot,
-					   args->v0.num_slots, args->v0.pbn,
-					   args->v0.aligned_pbn);
+			outp->ior->func->dp.vcpi(outp->ior, hidx,
+						 args->v0.start_slot,
+						 args->v0.num_slots,
+						 args->v0.pbn,
+						 args->v0.aligned_pbn);
 			return 0;
 		} else
 			return ret;
 	}
 		break;
-	case NV50_DISP_MTHD_V1_PIOR_PWR:
-		if (!func->pior.power)
-			return -ENODEV;
-		return func->pior.power(object, disp, data, size, head, outp);
 	default:
 		break;
 	}
@@ -291,7 +327,21 @@ static int
 nv50_disp_root_init_(struct nvkm_object *object)
 {
 	struct nv50_disp_root *root = nv50_disp_root(object);
-	return root->func->init(root);
+	struct nvkm_ior *ior;
+	int ret;
+
+	ret = root->func->init(root);
+	if (ret)
+		return ret;
+
+	/* Set 'normal' (ie. when it's attached to a head) state for
+	 * each output resource to 'fully enabled'.
+	 */
+	list_for_each_entry(ior, &root->disp->base.ior, head) {
+		ior->func->power(ior, true, true, true, true, true);
+	}
+
+	return 0;
 }
 
 static void *
@@ -352,6 +402,7 @@ int
 nv50_disp_root_init(struct nv50_disp_root *root)
 {
 	struct nv50_disp *disp = root->disp;
+	struct nvkm_head *head;
 	struct nvkm_device *device = disp->base.engine.subdev.device;
 	u32 tmp;
 	int i;
@@ -364,15 +415,15 @@ nv50_disp_root_init(struct nv50_disp_root *root)
 	nvkm_wr32(device, 0x610184, tmp);
 
 	/* ... CRTC caps */
-	for (i = 0; i < disp->base.head.nr; i++) {
-		tmp = nvkm_rd32(device, 0x616100 + (i * 0x800));
-		nvkm_wr32(device, 0x610190 + (i * 0x10), tmp);
-		tmp = nvkm_rd32(device, 0x616104 + (i * 0x800));
-		nvkm_wr32(device, 0x610194 + (i * 0x10), tmp);
-		tmp = nvkm_rd32(device, 0x616108 + (i * 0x800));
-		nvkm_wr32(device, 0x610198 + (i * 0x10), tmp);
-		tmp = nvkm_rd32(device, 0x61610c + (i * 0x800));
-		nvkm_wr32(device, 0x61019c + (i * 0x10), tmp);
+	list_for_each_entry(head, &disp->base.head, head) {
+		tmp = nvkm_rd32(device, 0x616100 + (head->id * 0x800));
+		nvkm_wr32(device, 0x610190 + (head->id * 0x10), tmp);
+		tmp = nvkm_rd32(device, 0x616104 + (head->id * 0x800));
+		nvkm_wr32(device, 0x610194 + (head->id * 0x10), tmp);
+		tmp = nvkm_rd32(device, 0x616108 + (head->id * 0x800));
+		nvkm_wr32(device, 0x610198 + (head->id * 0x10), tmp);
+		tmp = nvkm_rd32(device, 0x61610c + (head->id * 0x800));
+		nvkm_wr32(device, 0x61019c + (head->id * 0x10), tmp);
 	}
 
 	/* ... DAC caps */
diff --git a/drivers/gpu/drm/nouveau/nvkm/engine/disp/sorg84.c b/drivers/gpu/drm/nouveau/nvkm/engine/disp/sorg84.c
new file mode 100644
index 000000000000..f40b909b4ca2
--- /dev/null
+++ b/drivers/gpu/drm/nouveau/nvkm/engine/disp/sorg84.c
@@ -0,0 +1,38 @@
+/*
+ * Copyright 2017 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.
+ */
+#include "ior.h"
+
+static const struct nvkm_ior_func
+g84_sor = {
+	.state = nv50_sor_state,
+	.power = nv50_sor_power,
+	.clock = nv50_sor_clock,
+	.hdmi = {
+		.ctrl = g84_hdmi_ctrl,
+	},
+};
+
+int
+g84_sor_new(struct nvkm_disp *disp, int id)
+{
+	return nv50_sor_new_(&g84_sor, disp, id);
+}
diff --git a/drivers/gpu/drm/nouveau/nvkm/engine/disp/sorg94.c b/drivers/gpu/drm/nouveau/nvkm/engine/disp/sorg94.c
index 627b9ee1ddd2..49aeafde0031 100644
--- a/drivers/gpu/drm/nouveau/nvkm/engine/disp/sorg94.c
+++ b/drivers/gpu/drm/nouveau/nvkm/engine/disp/sorg94.c
@@ -21,58 +21,75 @@
  *
  * Authors: Ben Skeggs
  */
-#include "nv50.h"
-#include "outpdp.h"
+#include "ior.h"
 
 #include <subdev/timer.h>
 
-static inline u32
-g94_sor_soff(struct nvkm_output_dp *outp)
+void
+g94_sor_dp_watermark(struct nvkm_ior *sor, int head, u8 watermark)
 {
-	return (ffs(outp->base.info.or) - 1) * 0x800;
+	struct nvkm_device *device = sor->disp->engine.subdev.device;
+	const u32 loff = nv50_sor_link(sor);
+	nvkm_mask(device, 0x61c128 + loff, 0x0000003f, watermark);
 }
 
-static inline u32
-g94_sor_loff(struct nvkm_output_dp *outp)
+void
+g94_sor_dp_activesym(struct nvkm_ior *sor, int head,
+		     u8 TU, u8 VTUa, u8 VTUf, u8 VTUi)
 {
-	return g94_sor_soff(outp) + !(outp->base.info.sorconf.link & 1) * 0x80;
+	struct nvkm_device *device = sor->disp->engine.subdev.device;
+	const u32 loff = nv50_sor_link(sor);
+	nvkm_mask(device, 0x61c10c + loff, 0x000001fc, TU << 2);
+	nvkm_mask(device, 0x61c128 + loff, 0x010f7f00, VTUa << 24 |
+						       VTUf << 16 |
+						       VTUi << 8);
 }
 
-/*******************************************************************************
- * DisplayPort
- ******************************************************************************/
-u32
-g94_sor_dp_lane_map(struct nvkm_device *device, u8 lane)
+void
+g94_sor_dp_audio_sym(struct nvkm_ior *sor, int head, u16 h, u32 v)
 {
-	static const u8 gm100[] = { 0, 8, 16, 24 };
-	static const u8 mcp89[] = { 24, 16, 8, 0 }; /* thanks, apple.. */
-	static const u8   g94[] = { 16, 8, 0, 24 };
-	if (device->chipset >= 0x110)
-		return gm100[lane];
-	if (device->chipset == 0xaf)
-		return mcp89[lane];
-	return g94[lane];
+	struct nvkm_device *device = sor->disp->engine.subdev.device;
+	const u32 soff = nv50_ior_base(sor);
+	nvkm_mask(device, 0x61c1e8 + soff, 0x0000ffff, h);
+	nvkm_mask(device, 0x61c1ec + soff, 0x00ffffff, v);
 }
 
-static int
-g94_sor_dp_pattern(struct nvkm_output_dp *outp, int pattern)
+void
+g94_sor_dp_drive(struct nvkm_ior *sor, int ln, int pc, int dc, int pe, int pu)
 {
-	struct nvkm_device *device = outp->base.disp->engine.subdev.device;
-	const u32 loff = g94_sor_loff(outp);
+	struct nvkm_device *device = sor->disp->engine.subdev.device;
+	const u32  loff = nv50_sor_link(sor);
+	const u32 shift = sor->func->dp.lanes[ln] * 8;
+	u32 data[3];
+
+	data[0] = nvkm_rd32(device, 0x61c118 + loff) & ~(0x000000ff << shift);
+	data[1] = nvkm_rd32(device, 0x61c120 + loff) & ~(0x000000ff << shift);
+	data[2] = nvkm_rd32(device, 0x61c130 + loff);
+	if ((data[2] & 0x0000ff00) < (pu << 8) || ln == 0)
+		data[2] = (data[2] & ~0x0000ff00) | (pu << 8);
+	nvkm_wr32(device, 0x61c118 + loff, data[0] | (dc << shift));
+	nvkm_wr32(device, 0x61c120 + loff, data[1] | (pe << shift));
+	nvkm_wr32(device, 0x61c130 + loff, data[2]);
+}
+
+void
+g94_sor_dp_pattern(struct nvkm_ior *sor, int pattern)
+{
+	struct nvkm_device *device = sor->disp->engine.subdev.device;
+	const u32 loff = nv50_sor_link(sor);
 	nvkm_mask(device, 0x61c10c + loff, 0x0f000000, pattern << 24);
-	return 0;
 }
 
-int
-g94_sor_dp_lnk_pwr(struct nvkm_output_dp *outp, int nr)
+void
+g94_sor_dp_power(struct nvkm_ior *sor, int nr)
 {
-	struct nvkm_device *device = outp->base.disp->engine.subdev.device;
-	const u32 soff = g94_sor_soff(outp);
-	const u32 loff = g94_sor_loff(outp);
+	struct nvkm_device *device = sor->disp->engine.subdev.device;
+	const u32 soff = nv50_ior_base(sor);
+	const u32 loff = nv50_sor_link(sor);
 	u32 mask = 0, i;
 
 	for (i = 0; i < nr; i++)
-		mask |= 1 << (g94_sor_dp_lane_map(device, i) >> 3);
+		mask |= 1 << sor->func->dp.lanes[i];
 
 	nvkm_mask(device, 0x61c130 + loff, 0x0000000f, mask);
 	nvkm_mask(device, 0x61c034 + soff, 0x80000000, 0x80000000);
@@ -80,22 +97,21 @@ g94_sor_dp_lnk_pwr(struct nvkm_output_dp *outp, int nr)
 		if (!(nvkm_rd32(device, 0x61c034 + soff) & 0x80000000))
 			break;
 	);
-	return 0;
 }
 
-static int
-g94_sor_dp_lnk_ctl(struct nvkm_output_dp *outp, int nr, int bw, bool ef)
+int
+g94_sor_dp_links(struct nvkm_ior *sor, struct nvkm_i2c_aux *aux)
 {
-	struct nvkm_device *device = outp->base.disp->engine.subdev.device;
-	const u32 soff = g94_sor_soff(outp);
-	const u32 loff = g94_sor_loff(outp);
+	struct nvkm_device *device = sor->disp->engine.subdev.device;
+	const u32 soff = nv50_ior_base(sor);
+	const u32 loff = nv50_sor_link(sor);
 	u32 dpctrl = 0x00000000;
 	u32 clksor = 0x00000000;
 
-	dpctrl |= ((1 << nr) - 1) << 16;
-	if (ef)
+	dpctrl |= ((1 << sor->dp.nr) - 1) << 16;
+	if (sor->dp.ef)
 		dpctrl |= 0x00004000;
-	if (bw > 0x06)
+	if (sor->dp.bw > 0x06)
 		clksor |= 0x00040000;
 
 	nvkm_mask(device, 0x614300 + soff, 0x000c0000, clksor);
@@ -103,51 +119,165 @@ g94_sor_dp_lnk_ctl(struct nvkm_output_dp *outp, int nr, int bw, bool ef)
 	return 0;
 }
 
-static int
-g94_sor_dp_drv_ctl(struct nvkm_output_dp *outp, int ln, int vs, int pe, int pc)
+static bool
+g94_sor_war_needed(struct nvkm_ior *sor)
 {
-	struct nvkm_device *device = outp->base.disp->engine.subdev.device;
-	struct nvkm_bios *bios = device->bios;
-	const u32 shift = g94_sor_dp_lane_map(device, ln);
-	const u32 loff = g94_sor_loff(outp);
-	u32 addr, data[3];
-	u8  ver, hdr, cnt, len;
-	struct nvbios_dpout info;
-	struct nvbios_dpcfg ocfg;
-
-	addr = nvbios_dpout_match(bios, outp->base.info.hasht,
-					outp->base.info.hashm,
-				  &ver, &hdr, &cnt, &len, &info);
-	if (!addr)
-		return -ENODEV;
-
-	addr = nvbios_dpcfg_match(bios, addr, 0, vs, pe,
-				  &ver, &hdr, &cnt, &len, &ocfg);
-	if (!addr)
-		return -EINVAL;
+	struct nvkm_device *device = sor->disp->engine.subdev.device;
+	const u32 soff = nv50_ior_base(sor);
+	if (sor->asy.proto == TMDS) {
+		switch (nvkm_rd32(device, 0x614300 + soff) & 0x00030000) {
+		case 0x00000000:
+		case 0x00030000:
+			return true;
+		default:
+			break;
+		}
+	}
+	return false;
+}
 
-	data[0] = nvkm_rd32(device, 0x61c118 + loff) & ~(0x000000ff << shift);
-	data[1] = nvkm_rd32(device, 0x61c120 + loff) & ~(0x000000ff << shift);
-	data[2] = nvkm_rd32(device, 0x61c130 + loff);
-	if ((data[2] & 0x0000ff00) < (ocfg.tx_pu << 8) || ln == 0)
-		data[2] = (data[2] & ~0x0000ff00) | (ocfg.tx_pu << 8);
-	nvkm_wr32(device, 0x61c118 + loff, data[0] | (ocfg.dc << shift));
-	nvkm_wr32(device, 0x61c120 + loff, data[1] | (ocfg.pe << shift));
-	nvkm_wr32(device, 0x61c130 + loff, data[2]);
-	return 0;
+static void
+g94_sor_war_update_sppll1(struct nvkm_disp *disp)
+{
+	struct nvkm_device *device = disp->engine.subdev.device;
+	struct nvkm_ior *ior;
+	bool used = false;
+	u32 clksor;
+
+	list_for_each_entry(ior, &disp->ior, head) {
+		if (ior->type != SOR)
+			continue;
+
+		clksor = nvkm_rd32(device, 0x614300 + nv50_ior_base(ior));
+		switch (clksor & 0x03000000) {
+		case 0x02000000:
+		case 0x03000000:
+			used = true;
+			break;
+		default:
+			break;
+		}
+	}
+
+	if (used)
+		return;
+
+	nvkm_mask(device, 0x00e840, 0x80000000, 0x00000000);
+}
+
+static void
+g94_sor_war_3(struct nvkm_ior *sor)
+{
+	struct nvkm_device *device = sor->disp->engine.subdev.device;
+	const u32 soff = nv50_ior_base(sor);
+	u32 sorpwr;
+
+	if (!g94_sor_war_needed(sor))
+		return;
+
+	sorpwr = nvkm_rd32(device, 0x61c004 + soff);
+	if (sorpwr & 0x00000001) {
+		u32 seqctl = nvkm_rd32(device, 0x61c030 + soff);
+		u32  pd_pc = (seqctl & 0x00000f00) >> 8;
+		u32  pu_pc =  seqctl & 0x0000000f;
+
+		nvkm_wr32(device, 0x61c040 + soff + pd_pc * 4, 0x1f008000);
+
+		nvkm_msec(device, 2000,
+			if (!(nvkm_rd32(device, 0x61c030 + soff) & 0x10000000))
+				break;
+		);
+		nvkm_mask(device, 0x61c004 + soff, 0x80000001, 0x80000000);
+		nvkm_msec(device, 2000,
+			if (!(nvkm_rd32(device, 0x61c030 + soff) & 0x10000000))
+				break;
+		);
+
+		nvkm_wr32(device, 0x61c040 + soff + pd_pc * 4, 0x00002000);
+		nvkm_wr32(device, 0x61c040 + soff + pu_pc * 4, 0x1f000000);
+	}
+
+	nvkm_mask(device, 0x61c10c + soff, 0x00000001, 0x00000000);
+	nvkm_mask(device, 0x614300 + soff, 0x03000000, 0x00000000);
+
+	if (sorpwr & 0x00000001) {
+		nvkm_mask(device, 0x61c004 + soff, 0x80000001, 0x80000001);
+	}
+
+	g94_sor_war_update_sppll1(sor->disp);
+}
+
+static void
+g94_sor_war_2(struct nvkm_ior *sor)
+{
+	struct nvkm_device *device = sor->disp->engine.subdev.device;
+	const u32 soff = nv50_ior_base(sor);
+
+	if (!g94_sor_war_needed(sor))
+		return;
+
+	nvkm_mask(device, 0x00e840, 0x80000000, 0x80000000);
+	nvkm_mask(device, 0x614300 + soff, 0x03000000, 0x03000000);
+	nvkm_mask(device, 0x61c10c + soff, 0x00000001, 0x00000001);
+
+	nvkm_mask(device, 0x61c00c + soff, 0x0f000000, 0x00000000);
+	nvkm_mask(device, 0x61c008 + soff, 0xff000000, 0x14000000);
+	nvkm_usec(device, 400, NVKM_DELAY);
+	nvkm_mask(device, 0x61c008 + soff, 0xff000000, 0x00000000);
+	nvkm_mask(device, 0x61c00c + soff, 0x0f000000, 0x01000000);
+
+	if (nvkm_rd32(device, 0x61c004 + soff) & 0x00000001) {
+		u32 seqctl = nvkm_rd32(device, 0x61c030 + soff);
+		u32  pu_pc = seqctl & 0x0000000f;
+		nvkm_wr32(device, 0x61c040 + soff + pu_pc * 4, 0x1f008000);
+	}
+}
+
+void
+g94_sor_state(struct nvkm_ior *sor, struct nvkm_ior_state *state)
+{
+	struct nvkm_device *device = sor->disp->engine.subdev.device;
+	const u32 coff = sor->id * 8 + (state == &sor->arm) * 4;
+	u32 ctrl = nvkm_rd32(device, 0x610794 + coff);
+
+	state->proto_evo = (ctrl & 0x00000f00) >> 8;
+	switch (state->proto_evo) {
+	case 0: state->proto = LVDS; state->link = 1; break;
+	case 1: state->proto = TMDS; state->link = 1; break;
+	case 2: state->proto = TMDS; state->link = 2; break;
+	case 5: state->proto = TMDS; state->link = 3; break;
+	case 8: state->proto =   DP; state->link = 1; break;
+	case 9: state->proto =   DP; state->link = 2; break;
+	default:
+		state->proto = UNKNOWN;
+		break;
+	}
+
+	state->head = ctrl & 0x00000003;
+	nv50_pior_depth(sor, state, ctrl);
 }
 
-static const struct nvkm_output_dp_func
-g94_sor_dp_func = {
-	.pattern = g94_sor_dp_pattern,
-	.lnk_pwr = g94_sor_dp_lnk_pwr,
-	.lnk_ctl = g94_sor_dp_lnk_ctl,
-	.drv_ctl = g94_sor_dp_drv_ctl,
+static const struct nvkm_ior_func
+g94_sor = {
+	.state = g94_sor_state,
+	.power = nv50_sor_power,
+	.clock = nv50_sor_clock,
+	.war_2 = g94_sor_war_2,
+	.war_3 = g94_sor_war_3,
+	.dp = {
+		.lanes = { 2, 1, 0, 3},
+		.links = g94_sor_dp_links,
+		.power = g94_sor_dp_power,
+		.pattern = g94_sor_dp_pattern,
+		.drive = g94_sor_dp_drive,
+		.audio_sym = g94_sor_dp_audio_sym,
+		.activesym = g94_sor_dp_activesym,
+		.watermark = g94_sor_dp_watermark,
+	},
 };
 
 int
-g94_sor_dp_new(struct nvkm_disp *disp, int index, struct dcb_output *dcbE,
-	       struct nvkm_output **poutp)
+g94_sor_new(struct nvkm_disp *disp, int id)
 {
-	return nvkm_output_dp_new_(&g94_sor_dp_func, disp, index, dcbE, poutp);
+	return nv50_sor_new_(&g94_sor, disp, id);
 }
diff --git a/drivers/gpu/drm/nouveau/nvkm/engine/disp/sorgf119.c b/drivers/gpu/drm/nouveau/nvkm/engine/disp/sorgf119.c
index 6ffdaa65aa77..a2978a37b4f3 100644
--- a/drivers/gpu/drm/nouveau/nvkm/engine/disp/sorgf119.c
+++ b/drivers/gpu/drm/nouveau/nvkm/engine/disp/sorgf119.c
@@ -21,44 +21,94 @@
  *
  * Authors: Ben Skeggs
  */
-#include "nv50.h"
-#include "outpdp.h"
+#include "ior.h"
 
-static inline u32
-gf119_sor_soff(struct nvkm_output_dp *outp)
+#include <subdev/timer.h>
+
+void
+gf119_sor_dp_watermark(struct nvkm_ior *sor, int head, u8 watermark)
+{
+	struct nvkm_device *device = sor->disp->engine.subdev.device;
+	const u32 hoff = head * 0x800;
+	nvkm_mask(device, 0x616610 + hoff, 0x0800003f, 0x08000000 | watermark);
+}
+
+void
+gf119_sor_dp_audio_sym(struct nvkm_ior *sor, int head, u16 h, u32 v)
 {
-	return (ffs(outp->base.info.or) - 1) * 0x800;
+	struct nvkm_device *device = sor->disp->engine.subdev.device;
+	const u32 hoff = head * 0x800;
+	nvkm_mask(device, 0x616620 + hoff, 0x0000ffff, h);
+	nvkm_mask(device, 0x616624 + hoff, 0x00ffffff, v);
 }
 
-static inline u32
-gf119_sor_loff(struct nvkm_output_dp *outp)
+void
+gf119_sor_dp_audio(struct nvkm_ior *sor, int head, bool enable)
 {
-	return gf119_sor_soff(outp) + !(outp->base.info.sorconf.link & 1) * 0x80;
+	struct nvkm_device *device = sor->disp->engine.subdev.device;
+	const u32 hoff = 0x800 * head;
+	const u32 data = 0x80000000 | (0x00000001 * enable);
+	const u32 mask = 0x8000000d;
+	nvkm_mask(device, 0x616618 + hoff, mask, data);
+	nvkm_msec(device, 2000,
+		if (!(nvkm_rd32(device, 0x616618 + hoff) & 0x80000000))
+			break;
+	);
+}
+
+void
+gf119_sor_dp_vcpi(struct nvkm_ior *sor, int head,
+		  u8 slot, u8 slot_nr, u16 pbn, u16 aligned)
+{
+	struct nvkm_device *device = sor->disp->engine.subdev.device;
+	const u32 hoff = head * 0x800;
+
+	nvkm_mask(device, 0x616588 + hoff, 0x00003f3f, (slot_nr << 8) | slot);
+	nvkm_mask(device, 0x61658c + hoff, 0xffffffff, (aligned << 16) | pbn);
+}
+
+void
+gf119_sor_dp_drive(struct nvkm_ior *sor, int ln, int pc, int dc, int pe, int pu)
+{
+	struct nvkm_device *device = sor->disp->engine.subdev.device;
+	const u32  loff = nv50_sor_link(sor);
+	const u32 shift = sor->func->dp.lanes[ln] * 8;
+	u32 data[4];
+
+	data[0] = nvkm_rd32(device, 0x61c118 + loff) & ~(0x000000ff << shift);
+	data[1] = nvkm_rd32(device, 0x61c120 + loff) & ~(0x000000ff << shift);
+	data[2] = nvkm_rd32(device, 0x61c130 + loff);
+	if ((data[2] & 0x0000ff00) < (pu << 8) || ln == 0)
+		data[2] = (data[2] & ~0x0000ff00) | (pu << 8);
+	nvkm_wr32(device, 0x61c118 + loff, data[0] | (dc << shift));
+	nvkm_wr32(device, 0x61c120 + loff, data[1] | (pe << shift));
+	nvkm_wr32(device, 0x61c130 + loff, data[2]);
+	data[3] = nvkm_rd32(device, 0x61c13c + loff) & ~(0x000000ff << shift);
+	nvkm_wr32(device, 0x61c13c + loff, data[3] | (pc << shift));
 }
 
-static int
-gf119_sor_dp_pattern(struct nvkm_output_dp *outp, int pattern)
+void
+gf119_sor_dp_pattern(struct nvkm_ior *sor, int pattern)
 {
-	struct nvkm_device *device = outp->base.disp->engine.subdev.device;
-	const u32 soff = gf119_sor_soff(outp);
+	struct nvkm_device *device = sor->disp->engine.subdev.device;
+	const u32 soff = nv50_ior_base(sor);
 	nvkm_mask(device, 0x61c110 + soff, 0x0f0f0f0f, 0x01010101 * pattern);
-	return 0;
 }
 
 int
-gf119_sor_dp_lnk_ctl(struct nvkm_output_dp *outp, int nr, int bw, bool ef)
+gf119_sor_dp_links(struct nvkm_ior *sor, struct nvkm_i2c_aux *aux)
 {
-	struct nvkm_device *device = outp->base.disp->engine.subdev.device;
-	const u32 soff = gf119_sor_soff(outp);
-	const u32 loff = gf119_sor_loff(outp);
+	struct nvkm_device *device = sor->disp->engine.subdev.device;
+	const u32 soff = nv50_ior_base(sor);
+	const u32 loff = nv50_sor_link(sor);
 	u32 dpctrl = 0x00000000;
 	u32 clksor = 0x00000000;
 
-	clksor |= bw << 18;
-	dpctrl |= ((1 << nr) - 1) << 16;
-	if (outp->lt.mst)
+	clksor |= sor->dp.bw << 18;
+	dpctrl |= ((1 << sor->dp.nr) - 1) << 16;
+	if (sor->dp.mst)
 		dpctrl |= 0x40000000;
-	if (ef)
+	if (sor->dp.ef)
 		dpctrl |= 0x00004000;
 
 	nvkm_mask(device, 0x612300 + soff, 0x007c0000, clksor);
@@ -66,66 +116,77 @@ gf119_sor_dp_lnk_ctl(struct nvkm_output_dp *outp, int nr, int bw, bool ef)
 	return 0;
 }
 
-int
-gf119_sor_dp_drv_ctl(struct nvkm_output_dp *outp,
-		     int ln, int vs, int pe, int pc)
+void
+gf119_sor_clock(struct nvkm_ior *sor)
 {
-	struct nvkm_device *device = outp->base.disp->engine.subdev.device;
-	struct nvkm_bios *bios = device->bios;
-	const u32 shift = g94_sor_dp_lane_map(device, ln);
-	const u32 loff = gf119_sor_loff(outp);
-	u32 addr, data[4];
-	u8  ver, hdr, cnt, len;
-	struct nvbios_dpout info;
-	struct nvbios_dpcfg ocfg;
-
-	addr = nvbios_dpout_match(bios, outp->base.info.hasht,
-					outp->base.info.hashm,
-				  &ver, &hdr, &cnt, &len, &info);
-	if (!addr)
-		return -ENODEV;
-
-	addr = nvbios_dpcfg_match(bios, addr, pc, vs, pe,
-				  &ver, &hdr, &cnt, &len, &ocfg);
-	if (!addr)
-		return -EINVAL;
-
-	data[0] = nvkm_rd32(device, 0x61c118 + loff) & ~(0x000000ff << shift);
-	data[1] = nvkm_rd32(device, 0x61c120 + loff) & ~(0x000000ff << shift);
-	data[2] = nvkm_rd32(device, 0x61c130 + loff);
-	if ((data[2] & 0x0000ff00) < (ocfg.tx_pu << 8) || ln == 0)
-		data[2] = (data[2] & ~0x0000ff00) | (ocfg.tx_pu << 8);
-	nvkm_wr32(device, 0x61c118 + loff, data[0] | (ocfg.dc << shift));
-	nvkm_wr32(device, 0x61c120 + loff, data[1] | (ocfg.pe << shift));
-	nvkm_wr32(device, 0x61c130 + loff, data[2]);
-	data[3] = nvkm_rd32(device, 0x61c13c + loff) & ~(0x000000ff << shift);
-	nvkm_wr32(device, 0x61c13c + loff, data[3] | (ocfg.pc << shift));
-	return 0;
+	struct nvkm_device *device = sor->disp->engine.subdev.device;
+	const int  div = sor->asy.link == 3;
+	const u32 soff = nv50_ior_base(sor);
+	if (sor->asy.proto == TMDS) {
+		/* NFI why, but this sets DP_LINK_BW_2_7 when using TMDS. */
+		nvkm_mask(device, 0x612300 + soff, 0x007c0000, 0x0a << 18);
+	}
+	nvkm_mask(device, 0x612300 + soff, 0x00000707, (div << 8) | div);
 }
 
 void
-gf119_sor_dp_vcpi(struct nvkm_output_dp *outp, int head, u8 slot,
-		  u8 slot_nr, u16 pbn, u16 aligned)
+gf119_sor_state(struct nvkm_ior *sor, struct nvkm_ior_state *state)
 {
-	struct nvkm_device *device = outp->base.disp->engine.subdev.device;
-	const u32 hoff = head * 0x800;
+	struct nvkm_device *device = sor->disp->engine.subdev.device;
+	const u32 coff = (state == &sor->asy) * 0x20000 + sor->id * 0x20;
+	u32 ctrl = nvkm_rd32(device, 0x640200 + coff);
 
-	nvkm_mask(device, 0x616588 + hoff, 0x00003f3f, (slot_nr << 8) | slot);
-	nvkm_mask(device, 0x61658c + hoff, 0xffffffff, (aligned << 16) | pbn);
+	state->proto_evo = (ctrl & 0x00000f00) >> 8;
+	switch (state->proto_evo) {
+	case 0: state->proto = LVDS; state->link = 1; break;
+	case 1: state->proto = TMDS; state->link = 1; break;
+	case 2: state->proto = TMDS; state->link = 2; break;
+	case 5: state->proto = TMDS; state->link = 3; break;
+	case 8: state->proto =   DP; state->link = 1; break;
+	case 9: state->proto =   DP; state->link = 2; break;
+	default:
+		state->proto = UNKNOWN;
+		break;
+	}
+
+	state->head = ctrl & 0x0000000f;
+}
+
+int
+gf119_sor_new_(const struct nvkm_ior_func *func, struct nvkm_disp *disp, int id)
+{
+	struct nvkm_device *device = disp->engine.subdev.device;
+	if (!(nvkm_rd32(device, 0x612004) & (0x00000100 << id)))
+		return 0;
+	return nvkm_ior_new_(func, disp, SOR, id);
 }
 
-static const struct nvkm_output_dp_func
-gf119_sor_dp_func = {
-	.pattern = gf119_sor_dp_pattern,
-	.lnk_pwr = g94_sor_dp_lnk_pwr,
-	.lnk_ctl = gf119_sor_dp_lnk_ctl,
-	.drv_ctl = gf119_sor_dp_drv_ctl,
-	.vcpi = gf119_sor_dp_vcpi,
+static const struct nvkm_ior_func
+gf119_sor = {
+	.state = gf119_sor_state,
+	.power = nv50_sor_power,
+	.clock = gf119_sor_clock,
+	.hdmi = {
+		.ctrl = gf119_hdmi_ctrl,
+	},
+	.dp = {
+		.lanes = { 2, 1, 0, 3 },
+		.links = gf119_sor_dp_links,
+		.power = g94_sor_dp_power,
+		.pattern = gf119_sor_dp_pattern,
+		.vcpi = gf119_sor_dp_vcpi,
+		.audio = gf119_sor_dp_audio,
+		.audio_sym = gf119_sor_dp_audio_sym,
+		.watermark = gf119_sor_dp_watermark,
+	},
+	.hda = {
+		.hpd = gf119_hda_hpd,
+		.eld = gf119_hda_eld,
+	},
 };
 
 int
-gf119_sor_dp_new(struct nvkm_disp *disp, int index,
-		 struct dcb_output *dcbE, struct nvkm_output **poutp)
+gf119_sor_new(struct nvkm_disp *disp, int id)
 {
-	return nvkm_output_dp_new_(&gf119_sor_dp_func, disp, index, dcbE, poutp);
+	return gf119_sor_new_(&gf119_sor, disp, id);
 }
diff --git a/drivers/gpu/drm/nouveau/nvkm/engine/disp/sorgk104.c b/drivers/gpu/drm/nouveau/nvkm/engine/disp/sorgk104.c
new file mode 100644
index 000000000000..a1547bdf490b
--- /dev/null
+++ b/drivers/gpu/drm/nouveau/nvkm/engine/disp/sorgk104.c
@@ -0,0 +1,53 @@
+/*
+ * Copyright 2017 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.
+ */
+#include "ior.h"
+
+static const struct nvkm_ior_func
+gk104_sor = {
+	.state = gf119_sor_state,
+	.power = nv50_sor_power,
+	.clock = gf119_sor_clock,
+	.hdmi = {
+		.ctrl = gk104_hdmi_ctrl,
+	},
+	.dp = {
+		.lanes = { 2, 1, 0, 3 },
+		.links = gf119_sor_dp_links,
+		.power = g94_sor_dp_power,
+		.pattern = gf119_sor_dp_pattern,
+		.drive = gf119_sor_dp_drive,
+		.vcpi = gf119_sor_dp_vcpi,
+		.audio = gf119_sor_dp_audio,
+		.audio_sym = gf119_sor_dp_audio_sym,
+		.watermark = gf119_sor_dp_watermark,
+	},
+	.hda = {
+		.hpd = gf119_hda_hpd,
+		.eld = gf119_hda_eld,
+	},
+};
+
+int
+gk104_sor_new(struct nvkm_disp *disp, int id)
+{
+	return gf119_sor_new_(&gk104_sor, disp, id);
+}
diff --git a/drivers/gpu/drm/nouveau/nvkm/engine/disp/sorgm107.c b/drivers/gpu/drm/nouveau/nvkm/engine/disp/sorgm107.c
index 4cf8ad4d18ab..60230957d82b 100644
--- a/drivers/gpu/drm/nouveau/nvkm/engine/disp/sorgm107.c
+++ b/drivers/gpu/drm/nouveau/nvkm/engine/disp/sorgm107.c
@@ -21,34 +21,47 @@
  *
  * Authors: Ben Skeggs <bskeggs@redhat.com>
  */
-#include "nv50.h"
-#include "outpdp.h"
+#include "ior.h"
 
-int
-gm107_sor_dp_pattern(struct nvkm_output_dp *outp, int pattern)
+void
+gm107_sor_dp_pattern(struct nvkm_ior *sor, int pattern)
 {
-	struct nvkm_device *device = outp->base.disp->engine.subdev.device;
-	const u32 soff = outp->base.or * 0x800;
+	struct nvkm_device *device = sor->disp->engine.subdev.device;
+	const u32 soff = nv50_ior_base(sor);
 	const u32 data = 0x01010101 * pattern;
-	if (outp->base.info.sorconf.link & 1)
+	if (sor->asy.link & 1)
 		nvkm_mask(device, 0x61c110 + soff, 0x0f0f0f0f, data);
 	else
 		nvkm_mask(device, 0x61c12c + soff, 0x0f0f0f0f, data);
-	return 0;
 }
 
-static const struct nvkm_output_dp_func
-gm107_sor_dp_func = {
-	.pattern = gm107_sor_dp_pattern,
-	.lnk_pwr = g94_sor_dp_lnk_pwr,
-	.lnk_ctl = gf119_sor_dp_lnk_ctl,
-	.drv_ctl = gf119_sor_dp_drv_ctl,
-	.vcpi = gf119_sor_dp_vcpi,
+static const struct nvkm_ior_func
+gm107_sor = {
+	.state = gf119_sor_state,
+	.power = nv50_sor_power,
+	.clock = gf119_sor_clock,
+	.hdmi = {
+		.ctrl = gk104_hdmi_ctrl,
+	},
+	.dp = {
+		.lanes = { 0, 1, 2, 3 },
+		.links = gf119_sor_dp_links,
+		.power = g94_sor_dp_power,
+		.pattern = gm107_sor_dp_pattern,
+		.drive = gf119_sor_dp_drive,
+		.vcpi = gf119_sor_dp_vcpi,
+		.audio = gf119_sor_dp_audio,
+		.audio_sym = gf119_sor_dp_audio_sym,
+		.watermark = gf119_sor_dp_watermark,
+	},
+	.hda = {
+		.hpd = gf119_hda_hpd,
+		.eld = gf119_hda_eld,
+	},
 };
 
 int
-gm107_sor_dp_new(struct nvkm_disp *disp, int index,
-		 struct dcb_output *dcbE, struct nvkm_output **poutp)
+gm107_sor_new(struct nvkm_disp *disp, int id)
 {
-	return nvkm_output_dp_new_(&gm107_sor_dp_func, disp, index, dcbE, poutp);
+	return gf119_sor_new_(&gm107_sor, disp, id);
 }
diff --git a/drivers/gpu/drm/nouveau/nvkm/engine/disp/sorgm200.c b/drivers/gpu/drm/nouveau/nvkm/engine/disp/sorgm200.c
index 81b788fa61be..f9b8107aa2a2 100644
--- a/drivers/gpu/drm/nouveau/nvkm/engine/disp/sorgm200.c
+++ b/drivers/gpu/drm/nouveau/nvkm/engine/disp/sorgm200.c
@@ -21,111 +21,104 @@
  *
  * Authors: Ben Skeggs
  */
-#include "nv50.h"
-#include "outpdp.h"
+#include "ior.h"
 
-#include <subdev/timer.h>
-
-static inline u32
-gm200_sor_soff(struct nvkm_output_dp *outp)
-{
-	return (ffs(outp->base.info.or) - 1) * 0x800;
-}
-
-static inline u32
-gm200_sor_loff(struct nvkm_output_dp *outp)
+static void
+gm200_sor_dp_drive(struct nvkm_ior *sor, int ln, int pc, int dc, int pe, int pu)
 {
-	return gm200_sor_soff(outp) + !(outp->base.info.sorconf.link & 1) * 0x80;
-}
+	struct nvkm_device *device = sor->disp->engine.subdev.device;
+	const u32  loff = nv50_sor_link(sor);
+	const u32 shift = sor->func->dp.lanes[ln] * 8;
+	u32 data[4];
 
-void
-gm200_sor_magic(struct nvkm_output *outp)
-{
-	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);
-	if (outp->info.sorconf.link & 2)
-		nvkm_mask(device, 0x612388 + soff, 0x0000001f, 0x00000010 | data);
-}
+	pu &= 0x0f;
 
-static inline u32
-gm200_sor_dp_lane_map(struct nvkm_device *device, u8 lane)
-{
-	return lane * 0x08;
+	data[0] = nvkm_rd32(device, 0x61c118 + loff) & ~(0x000000ff << shift);
+	data[1] = nvkm_rd32(device, 0x61c120 + loff) & ~(0x000000ff << shift);
+	data[2] = nvkm_rd32(device, 0x61c130 + loff);
+	if ((data[2] & 0x00000f00) < (pu << 8) || ln == 0)
+		data[2] = (data[2] & ~0x00000f00) | (pu << 8);
+	nvkm_wr32(device, 0x61c118 + loff, data[0] | (dc << shift));
+	nvkm_wr32(device, 0x61c120 + loff, data[1] | (pe << shift));
+	nvkm_wr32(device, 0x61c130 + loff, data[2]);
+	data[3] = nvkm_rd32(device, 0x61c13c + loff) & ~(0x000000ff << shift);
+	nvkm_wr32(device, 0x61c13c + loff, data[3] | (pc << shift));
 }
 
-static int
-gm200_sor_dp_lnk_pwr(struct nvkm_output_dp *outp, int nr)
+static void
+gm200_sor_route_set(struct nvkm_outp *outp, struct nvkm_ior *ior)
 {
-	struct nvkm_device *device = outp->base.disp->engine.subdev.device;
-	const u32 soff = gm200_sor_soff(outp);
-	const u32 loff = gm200_sor_loff(outp);
-	u32 mask = 0, i;
+	struct nvkm_device *device = outp->disp->engine.subdev.device;
+	const u32 moff = __ffs(outp->info.or) * 0x100;
+	const u32  sor = ior ? ior->id + 1 : 0;
+	u32 link = ior ? (ior->asy.link == 2) : 0;
 
-	for (i = 0; i < nr; i++)
-		mask |= 1 << (gm200_sor_dp_lane_map(device, i) >> 3);
+	if (outp->info.sorconf.link & 1) {
+		nvkm_mask(device, 0x612308 + moff, 0x0000001f, link << 4 | sor);
+		link++;
+	}
 
-	nvkm_mask(device, 0x61c130 + loff, 0x0000000f, mask);
-	nvkm_mask(device, 0x61c034 + soff, 0x80000000, 0x80000000);
-	nvkm_msec(device, 2000,
-		if (!(nvkm_rd32(device, 0x61c034 + soff) & 0x80000000))
-			break;
-	);
-	return 0;
+	if (outp->info.sorconf.link & 2)
+		nvkm_mask(device, 0x612388 + moff, 0x0000001f, link << 4 | sor);
 }
 
 static int
-gm200_sor_dp_drv_ctl(struct nvkm_output_dp *outp,
-		     int ln, int vs, int pe, int pc)
+gm200_sor_route_get(struct nvkm_outp *outp, int *link)
 {
-	struct nvkm_device *device = outp->base.disp->engine.subdev.device;
-	struct nvkm_bios *bios = device->bios;
-	const u32 shift = gm200_sor_dp_lane_map(device, ln);
-	const u32 loff = gm200_sor_loff(outp);
-	u32 addr, data[4];
-	u8  ver, hdr, cnt, len;
-	struct nvbios_dpout info;
-	struct nvbios_dpcfg ocfg;
+	struct nvkm_device *device = outp->disp->engine.subdev.device;
+	const int sublinks = outp->info.sorconf.link;
+	int lnk[2], sor[2], m, s;
 
-	addr = nvbios_dpout_match(bios, outp->base.info.hasht,
-					outp->base.info.hashm,
-				  &ver, &hdr, &cnt, &len, &info);
-	if (!addr)
-		return -ENODEV;
+	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];
+		}
+	}
 
-	addr = nvbios_dpcfg_match(bios, addr, pc, vs, pe,
-				  &ver, &hdr, &cnt, &len, &ocfg);
-	if (!addr)
-		return -EINVAL;
-	ocfg.tx_pu &= 0x0f;
+	if (sublinks == 3) {
+		if (sor[0] != sor[1] || WARN_ON(lnk[0] || !lnk[1]))
+			return -1;
+	}
 
-	data[0] = nvkm_rd32(device, 0x61c118 + loff) & ~(0x000000ff << shift);
-	data[1] = nvkm_rd32(device, 0x61c120 + loff) & ~(0x000000ff << shift);
-	data[2] = nvkm_rd32(device, 0x61c130 + loff);
-	if ((data[2] & 0x00000f00) < (ocfg.tx_pu << 8) || ln == 0)
-		data[2] = (data[2] & ~0x00000f00) | (ocfg.tx_pu << 8);
-	nvkm_wr32(device, 0x61c118 + loff, data[0] | (ocfg.dc << shift));
-	nvkm_wr32(device, 0x61c120 + loff, data[1] | (ocfg.pe << shift));
-	nvkm_wr32(device, 0x61c130 + loff, data[2]);
-	data[3] = nvkm_rd32(device, 0x61c13c + loff) & ~(0x000000ff << shift);
-	nvkm_wr32(device, 0x61c13c + loff, data[3] | (ocfg.pc << shift));
-	return 0;
+	return ((sublinks & 1) ? sor[0] : sor[1]) - 1;
 }
 
-static const struct nvkm_output_dp_func
-gm200_sor_dp_func = {
-	.pattern = gm107_sor_dp_pattern,
-	.lnk_pwr = gm200_sor_dp_lnk_pwr,
-	.lnk_ctl = gf119_sor_dp_lnk_ctl,
-	.drv_ctl = gm200_sor_dp_drv_ctl,
-	.vcpi = gf119_sor_dp_vcpi,
+static const struct nvkm_ior_func
+gm200_sor = {
+	.route = {
+		.get = gm200_sor_route_get,
+		.set = gm200_sor_route_set,
+	},
+	.state = gf119_sor_state,
+	.power = nv50_sor_power,
+	.clock = gf119_sor_clock,
+	.hdmi = {
+		.ctrl = gk104_hdmi_ctrl,
+	},
+	.dp = {
+		.lanes = { 0, 1, 2, 3 },
+		.links = gf119_sor_dp_links,
+		.power = g94_sor_dp_power,
+		.pattern = gm107_sor_dp_pattern,
+		.drive = gm200_sor_dp_drive,
+		.vcpi = gf119_sor_dp_vcpi,
+		.audio = gf119_sor_dp_audio,
+		.audio_sym = gf119_sor_dp_audio_sym,
+		.watermark = gf119_sor_dp_watermark,
+	},
+	.hda = {
+		.hpd = gf119_hda_hpd,
+		.eld = gf119_hda_eld,
+	},
 };
 
 int
-gm200_sor_dp_new(struct nvkm_disp *disp, int index, struct dcb_output *dcbE,
-		 struct nvkm_output **poutp)
+gm200_sor_new(struct nvkm_disp *disp, int id)
 {
-	return nvkm_output_dp_new_(&gm200_sor_dp_func, disp, index, dcbE, poutp);
+	return gf119_sor_new_(&gm200_sor, disp, id);
 }
diff --git a/drivers/gpu/drm/nouveau/nvkm/engine/disp/sorgt215.c b/drivers/gpu/drm/nouveau/nvkm/engine/disp/sorgt215.c
new file mode 100644
index 000000000000..da228b54b43e
--- /dev/null
+++ b/drivers/gpu/drm/nouveau/nvkm/engine/disp/sorgt215.c
@@ -0,0 +1,69 @@
+/*
+ * Copyright 2017 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.
+ */
+#include "ior.h"
+
+#include <subdev/timer.h>
+
+void
+gt215_sor_dp_audio(struct nvkm_ior *sor, int head, bool enable)
+{
+	struct nvkm_device *device = sor->disp->engine.subdev.device;
+	const u32 soff = nv50_ior_base(sor);
+	const u32 data = 0x80000000 | (0x00000001 * enable);
+	const u32 mask = 0x8000000d;
+	nvkm_mask(device, 0x61c1e0 + soff, mask, data);
+	nvkm_msec(device, 2000,
+		if (!(nvkm_rd32(device, 0x61c1e0 + soff) & 0x80000000))
+			break;
+	);
+}
+
+static const struct nvkm_ior_func
+gt215_sor = {
+	.state = g94_sor_state,
+	.power = nv50_sor_power,
+	.clock = nv50_sor_clock,
+	.hdmi = {
+		.ctrl = gt215_hdmi_ctrl,
+	},
+	.dp = {
+		.lanes = { 2, 1, 0, 3 },
+		.links = g94_sor_dp_links,
+		.power = g94_sor_dp_power,
+		.pattern = g94_sor_dp_pattern,
+		.drive = g94_sor_dp_drive,
+		.audio = gt215_sor_dp_audio,
+		.audio_sym = g94_sor_dp_audio_sym,
+		.activesym = g94_sor_dp_activesym,
+		.watermark = g94_sor_dp_watermark,
+	},
+	.hda = {
+		.hpd = gt215_hda_hpd,
+		.eld = gt215_hda_eld,
+	},
+};
+
+int
+gt215_sor_new(struct nvkm_disp *disp, int id)
+{
+	return nv50_sor_new_(&gt215_sor, disp, id);
+}
diff --git a/drivers/gpu/drm/nouveau/nvkm/engine/disp/sormcp77.c b/drivers/gpu/drm/nouveau/nvkm/engine/disp/sormcp77.c
new file mode 100644
index 000000000000..c0179ccb956d
--- /dev/null
+++ b/drivers/gpu/drm/nouveau/nvkm/engine/disp/sormcp77.c
@@ -0,0 +1,48 @@
+/*
+ * Copyright 2017 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.
+ */
+#include "ior.h"
+
+static const struct nvkm_ior_func
+mcp77_sor = {
+	.state = g94_sor_state,
+	.power = nv50_sor_power,
+	.clock = nv50_sor_clock,
+	.hdmi = {
+		.ctrl = g84_hdmi_ctrl,
+	},
+	.dp = {
+		.lanes = { 2, 1, 0, 3},
+		.links = g94_sor_dp_links,
+		.power = g94_sor_dp_power,
+		.pattern = g94_sor_dp_pattern,
+		.drive = g94_sor_dp_drive,
+		.audio_sym = g94_sor_dp_audio_sym,
+		.activesym = g94_sor_dp_activesym,
+		.watermark = g94_sor_dp_watermark,
+	},
+};
+
+int
+mcp77_sor_new(struct nvkm_disp *disp, int id)
+{
+	return nv50_sor_new_(&mcp77_sor, disp, id);
+}
diff --git a/drivers/gpu/drm/nouveau/nvkm/engine/disp/sormcp89.c b/drivers/gpu/drm/nouveau/nvkm/engine/disp/sormcp89.c
new file mode 100644
index 000000000000..9bb01cd96697
--- /dev/null
+++ b/drivers/gpu/drm/nouveau/nvkm/engine/disp/sormcp89.c
@@ -0,0 +1,53 @@
+/*
+ * Copyright 2017 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.
+ */
+#include "ior.h"
+
+static const struct nvkm_ior_func
+mcp89_sor = {
+	.state = g94_sor_state,
+	.power = nv50_sor_power,
+	.clock = nv50_sor_clock,
+	.hdmi = {
+		.ctrl = gt215_hdmi_ctrl,
+	},
+	.dp = {
+		.lanes = { 3, 2, 1, 0 },
+		.links = g94_sor_dp_links,
+		.power = g94_sor_dp_power,
+		.pattern = g94_sor_dp_pattern,
+		.drive = g94_sor_dp_drive,
+		.audio = gt215_sor_dp_audio,
+		.audio_sym = g94_sor_dp_audio_sym,
+		.activesym = g94_sor_dp_activesym,
+		.watermark = g94_sor_dp_watermark,
+	},
+	.hda = {
+		.hpd = gt215_hda_hpd,
+		.eld = gt215_hda_eld,
+	},
+};
+
+int
+mcp89_sor_new(struct nvkm_disp *disp, int id)
+{
+	return nv50_sor_new_(&mcp89_sor, disp, id);
+}
diff --git a/drivers/gpu/drm/nouveau/nvkm/engine/disp/sornv50.c b/drivers/gpu/drm/nouveau/nvkm/engine/disp/sornv50.c
index 53596bed3c36..f3ebd0c22e7d 100644
--- a/drivers/gpu/drm/nouveau/nvkm/engine/disp/sornv50.c
+++ b/drivers/gpu/drm/nouveau/nvkm/engine/disp/sornv50.c
@@ -21,59 +21,87 @@
  *
  * Authors: Ben Skeggs
  */
-#include "nv50.h"
-#include "outp.h"
+#include "ior.h"
 
-#include <core/client.h>
 #include <subdev/timer.h>
 
-#include <nvif/cl5070.h>
-#include <nvif/unpack.h>
-
-int
-nv50_sor_power(NV50_DISP_MTHD_V1)
+void
+nv50_sor_clock(struct nvkm_ior *sor)
 {
-	struct nvkm_device *device = disp->base.engine.subdev.device;
-	union {
-		struct nv50_disp_sor_pwr_v0 v0;
-	} *args = data;
-	const u32 soff = outp->or * 0x800;
-	u32 stat;
-	int ret = -ENOSYS;
-
-	nvif_ioctl(object, "disp sor pwr size %d\n", size);
-	if (!(ret = nvif_unpack(ret, &data, &size, args->v0, 0, 0, false))) {
-		nvif_ioctl(object, "disp sor pwr vers %d state %d\n",
-			   args->v0.version, args->v0.state);
-		stat = !!args->v0.state;
-	} else
-		return ret;
-
+	struct nvkm_device *device = sor->disp->engine.subdev.device;
+	const int  div = sor->asy.link == 3;
+	const u32 soff = nv50_ior_base(sor);
+	nvkm_mask(device, 0x614300 + soff, 0x00000707, (div << 8) | div);
+}
 
+static void
+nv50_sor_power_wait(struct nvkm_device *device, u32 soff)
+{
 	nvkm_msec(device, 2000,
 		if (!(nvkm_rd32(device, 0x61c004 + soff) & 0x80000000))
 			break;
 	);
-	nvkm_mask(device, 0x61c004 + soff, 0x80000001, 0x80000000 | stat);
-	nvkm_msec(device, 2000,
-		if (!(nvkm_rd32(device, 0x61c004 + soff) & 0x80000000))
-			break;
-	);
+}
+
+void
+nv50_sor_power(struct nvkm_ior *sor, bool normal, bool pu,
+	       bool data, bool vsync, bool hsync)
+{
+	struct nvkm_device *device = sor->disp->engine.subdev.device;
+	const u32  soff = nv50_ior_base(sor);
+	const u32 shift = normal ? 0 : 16;
+	const u32 state = 0x80000000 | (0x00000001 * !!pu) << shift;
+	const u32 field = 0x80000000 | (0x00000001 << shift);
+
+	nv50_sor_power_wait(device, soff);
+	nvkm_mask(device, 0x61c004 + soff, field, state);
+	nv50_sor_power_wait(device, soff);
+
 	nvkm_msec(device, 2000,
 		if (!(nvkm_rd32(device, 0x61c030 + soff) & 0x10000000))
 			break;
 	);
-	return 0;
 }
 
-static const struct nvkm_output_func
-nv50_sor_output_func = {
+void
+nv50_sor_state(struct nvkm_ior *sor, struct nvkm_ior_state *state)
+{
+	struct nvkm_device *device = sor->disp->engine.subdev.device;
+	const u32 coff = sor->id * 8 + (state == &sor->arm) * 4;
+	u32 ctrl = nvkm_rd32(device, 0x610b70 + coff);
+
+	state->proto_evo = (ctrl & 0x00000f00) >> 8;
+	switch (state->proto_evo) {
+	case 0: state->proto = LVDS; state->link = 1; break;
+	case 1: state->proto = TMDS; state->link = 1; break;
+	case 2: state->proto = TMDS; state->link = 2; break;
+	case 5: state->proto = TMDS; state->link = 3; break;
+	default:
+		state->proto = UNKNOWN;
+		break;
+	}
+
+	state->head = ctrl & 0x00000003;
+}
+
+int
+nv50_sor_new_(const struct nvkm_ior_func *func, struct nvkm_disp *disp, int id)
+{
+	struct nvkm_device *device = disp->engine.subdev.device;
+	if (!(nvkm_rd32(device, 0x610184) & (0x01000000 << id)))
+		return 0;
+	return nvkm_ior_new_(func, disp, SOR, id);
+}
+
+static const struct nvkm_ior_func
+nv50_sor = {
+	.state = nv50_sor_state,
+	.power = nv50_sor_power,
+	.clock = nv50_sor_clock,
 };
 
 int
-nv50_sor_output_new(struct nvkm_disp *disp, int index,
-		    struct dcb_output *dcbE, struct nvkm_output **poutp)
+nv50_sor_new(struct nvkm_disp *disp, int id)
 {
-	return nvkm_output_new_(&nv50_sor_output_func, disp,
-				index, dcbE, poutp);
+	return nv50_sor_new_(&nv50_sor, disp, id);
 }
diff --git a/drivers/gpu/drm/nouveau/nvkm/subdev/bios/iccsense.c b/drivers/gpu/drm/nouveau/nvkm/subdev/bios/iccsense.c
index 3953d11844ea..23caef8df17f 100644
--- a/drivers/gpu/drm/nouveau/nvkm/subdev/bios/iccsense.c
+++ b/drivers/gpu/drm/nouveau/nvkm/subdev/bios/iccsense.c
@@ -87,7 +87,10 @@ nvbios_iccsense_parse(struct nvkm_bios *bios, struct nvbios_iccsense *iccsense)
 
 		switch(ver) {
 		case 0x10:
-			rail->mode = nvbios_rd08(bios, entry + 0x1);
+			if ((nvbios_rd08(bios, entry + 0x1) & 0xf8) == 0xf8)
+				rail->mode = 1;
+			else
+				rail->mode = 0;
 			rail->extdev_id = nvbios_rd08(bios, entry + 0x2);
 			res_start = 0x3;
 			break;
diff --git a/drivers/gpu/drm/nouveau/nvkm/subdev/bios/init.c b/drivers/gpu/drm/nouveau/nvkm/subdev/bios/init.c
index 38ed09fd3d2f..b58ee99f7bfc 100644
--- a/drivers/gpu/drm/nouveau/nvkm/subdev/bios/init.c
+++ b/drivers/gpu/drm/nouveau/nvkm/subdev/bios/init.c
@@ -37,7 +37,7 @@
 #include <subdev/vga.h>
 
 #define bioslog(lvl, fmt, args...) do {                                        \
-	nvkm_printk(init->subdev, lvl, info, "0x%04x[%c]: "fmt,                \
+	nvkm_printk(init->subdev, lvl, info, "0x%08x[%c]: "fmt,                \
 		    init->offset, init_exec(init) ?                            \
 		    '0' + (init->nested - 1) : ' ', ##args);                   \
 } while(0)
@@ -87,8 +87,8 @@ static inline int
 init_or(struct nvbios_init *init)
 {
 	if (init_exec(init)) {
-		if (init->outp)
-			return ffs(init->outp->or) - 1;
+		if (init->or >= 0)
+			return init->or;
 		error("script needs OR!!\n");
 	}
 	return 0;
@@ -98,20 +98,20 @@ static inline int
 init_link(struct nvbios_init *init)
 {
 	if (init_exec(init)) {
-		if (init->outp)
-			return !(init->outp->sorconf.link & 1);
+		if (init->link)
+			return init->link == 2;
 		error("script needs OR link\n");
 	}
 	return 0;
 }
 
 static inline int
-init_crtc(struct nvbios_init *init)
+init_head(struct nvbios_init *init)
 {
 	if (init_exec(init)) {
-		if (init->crtc >= 0)
-			return init->crtc;
-		error("script needs crtc\n");
+		if (init->head >= 0)
+			return init->head;
+		error("script needs head\n");
 	}
 	return 0;
 }
@@ -119,7 +119,7 @@ init_crtc(struct nvbios_init *init)
 static u8
 init_conn(struct nvbios_init *init)
 {
-	struct nvkm_bios *bios = init->bios;
+	struct nvkm_bios *bios = init->subdev->device->bios;
 	struct nvbios_connE connE;
 	u8  ver, hdr;
 	u32 conn;
@@ -141,7 +141,7 @@ init_conn(struct nvbios_init *init)
 static inline u32
 init_nvreg(struct nvbios_init *init, u32 reg)
 {
-	struct nvkm_devinit *devinit = init->bios->subdev.device->devinit;
+	struct nvkm_devinit *devinit = init->subdev->device->devinit;
 
 	/* C51 (at least) sometimes has the lower bits set which the VBIOS
 	 * interprets to mean that access needs to go through certain IO
@@ -154,9 +154,9 @@ init_nvreg(struct nvbios_init *init, u32 reg)
 	/* GF8+ display scripts need register addresses mangled a bit to
 	 * select a specific CRTC/OR
 	 */
-	if (init->bios->subdev.device->card_type >= NV_50) {
+	if (init->subdev->device->card_type >= NV_50) {
 		if (reg & 0x80000000) {
-			reg += init_crtc(init) * 0x800;
+			reg += init_head(init) * 0x800;
 			reg &= ~0x80000000;
 		}
 
@@ -179,7 +179,7 @@ init_nvreg(struct nvbios_init *init, u32 reg)
 static u32
 init_rd32(struct nvbios_init *init, u32 reg)
 {
-	struct nvkm_device *device = init->bios->subdev.device;
+	struct nvkm_device *device = init->subdev->device;
 	reg = init_nvreg(init, reg);
 	if (reg != ~0 && init_exec(init))
 		return nvkm_rd32(device, reg);
@@ -189,7 +189,7 @@ init_rd32(struct nvbios_init *init, u32 reg)
 static void
 init_wr32(struct nvbios_init *init, u32 reg, u32 val)
 {
-	struct nvkm_device *device = init->bios->subdev.device;
+	struct nvkm_device *device = init->subdev->device;
 	reg = init_nvreg(init, reg);
 	if (reg != ~0 && init_exec(init))
 		nvkm_wr32(device, reg, val);
@@ -198,7 +198,7 @@ init_wr32(struct nvbios_init *init, u32 reg, u32 val)
 static u32
 init_mask(struct nvbios_init *init, u32 reg, u32 mask, u32 val)
 {
-	struct nvkm_device *device = init->bios->subdev.device;
+	struct nvkm_device *device = init->subdev->device;
 	reg = init_nvreg(init, reg);
 	if (reg != ~0 && init_exec(init)) {
 		u32 tmp = nvkm_rd32(device, reg);
@@ -212,7 +212,7 @@ static u8
 init_rdport(struct nvbios_init *init, u16 port)
 {
 	if (init_exec(init))
-		return nvkm_rdport(init->subdev->device, init->crtc, port);
+		return nvkm_rdport(init->subdev->device, init->head, port);
 	return 0x00;
 }
 
@@ -220,7 +220,7 @@ static void
 init_wrport(struct nvbios_init *init, u16 port, u8 value)
 {
 	if (init_exec(init))
-		nvkm_wrport(init->subdev->device, init->crtc, port, value);
+		nvkm_wrport(init->subdev->device, init->head, port, value);
 }
 
 static u8
@@ -228,7 +228,7 @@ init_rdvgai(struct nvbios_init *init, u16 port, u8 index)
 {
 	struct nvkm_subdev *subdev = init->subdev;
 	if (init_exec(init)) {
-		int head = init->crtc < 0 ? 0 : init->crtc;
+		int head = init->head < 0 ? 0 : init->head;
 		return nvkm_rdvgai(subdev->device, head, port, index);
 	}
 	return 0x00;
@@ -242,25 +242,25 @@ init_wrvgai(struct nvbios_init *init, u16 port, u8 index, u8 value)
 	/* force head 0 for updates to cr44, it only exists on first head */
 	if (device->card_type < NV_50) {
 		if (port == 0x03d4 && index == 0x44)
-			init->crtc = 0;
+			init->head = 0;
 	}
 
 	if (init_exec(init)) {
-		int head = init->crtc < 0 ? 0 : init->crtc;
+		int head = init->head < 0 ? 0 : init->head;
 		nvkm_wrvgai(device, head, port, index, value);
 	}
 
 	/* select head 1 if cr44 write selected it */
 	if (device->card_type < NV_50) {
 		if (port == 0x03d4 && index == 0x44 && value == 3)
-			init->crtc = 1;
+			init->head = 1;
 	}
 }
 
 static struct i2c_adapter *
 init_i2c(struct nvbios_init *init, int index)
 {
-	struct nvkm_i2c *i2c = init->bios->subdev.device->i2c;
+	struct nvkm_i2c *i2c = init->subdev->device->i2c;
 	struct nvkm_i2c_bus *bus;
 
 	if (index == 0xff) {
@@ -300,7 +300,7 @@ init_wri2cr(struct nvbios_init *init, u8 index, u8 addr, u8 reg, u8 val)
 static struct nvkm_i2c_aux *
 init_aux(struct nvbios_init *init)
 {
-	struct nvkm_i2c *i2c = init->bios->subdev.device->i2c;
+	struct nvkm_i2c *i2c = init->subdev->device->i2c;
 	if (!init->outp) {
 		if (init_exec(init))
 			error("script needs output for aux\n");
@@ -341,7 +341,7 @@ init_wrauxr(struct nvbios_init *init, u32 addr, u8 data)
 static void
 init_prog_pll(struct nvbios_init *init, u32 id, u32 freq)
 {
-	struct nvkm_devinit *devinit = init->bios->subdev.device->devinit;
+	struct nvkm_devinit *devinit = init->subdev->device->devinit;
 	if (init_exec(init)) {
 		int ret = nvkm_devinit_pll_set(devinit, id, freq);
 		if (ret)
@@ -374,7 +374,7 @@ init_table(struct nvkm_bios *bios, u16 *len)
 static u16
 init_table_(struct nvbios_init *init, u16 offset, const char *name)
 {
-	struct nvkm_bios *bios = init->bios;
+	struct nvkm_bios *bios = init->subdev->device->bios;
 	u16 len, data = init_table(bios, &len);
 	if (data) {
 		if (len >= offset + 2) {
@@ -406,7 +406,7 @@ init_table_(struct nvbios_init *init, u16 offset, const char *name)
 static u16
 init_script(struct nvkm_bios *bios, int index)
 {
-	struct nvbios_init init = { .bios = bios };
+	struct nvbios_init init = { .subdev = &bios->subdev };
 	u16 bmp_ver = bmp_version(bios), data;
 
 	if (bmp_ver && bmp_ver < 0x0510) {
@@ -436,7 +436,7 @@ init_unknown_script(struct nvkm_bios *bios)
 static u8
 init_ram_restrict_group_count(struct nvbios_init *init)
 {
-	return nvbios_ramcfg_count(init->bios);
+	return nvbios_ramcfg_count(init->subdev->device->bios);
 }
 
 static u8
@@ -450,7 +450,7 @@ init_ram_restrict(struct nvbios_init *init)
 	 * Preserving the non-caching behaviour on earlier chipsets just
 	 * in case *not* re-reading the strap causes similar breakage.
 	 */
-	if (!init->ramcfg || init->bios->version.major < 0x70)
+	if (!init->ramcfg || init->subdev->device->bios->version.major < 0x70)
 		init->ramcfg = 0x80000000 | nvbios_ramcfg_index(init->subdev);
 	return (init->ramcfg & 0x7fffffff);
 }
@@ -458,7 +458,7 @@ init_ram_restrict(struct nvbios_init *init)
 static u8
 init_xlat_(struct nvbios_init *init, u8 index, u8 offset)
 {
-	struct nvkm_bios *bios = init->bios;
+	struct nvkm_bios *bios = init->subdev->device->bios;
 	u16 table = init_xlat_table(init);
 	if (table) {
 		u16 data = nvbios_rd16(bios, table + (index * 2));
@@ -476,7 +476,7 @@ init_xlat_(struct nvbios_init *init, u8 index, u8 offset)
 static bool
 init_condition_met(struct nvbios_init *init, u8 cond)
 {
-	struct nvkm_bios *bios = init->bios;
+	struct nvkm_bios *bios = init->subdev->device->bios;
 	u16 table = init_condition_table(init);
 	if (table) {
 		u32 reg = nvbios_rd32(bios, table + (cond * 12) + 0);
@@ -492,7 +492,7 @@ init_condition_met(struct nvbios_init *init, u8 cond)
 static bool
 init_io_condition_met(struct nvbios_init *init, u8 cond)
 {
-	struct nvkm_bios *bios = init->bios;
+	struct nvkm_bios *bios = init->subdev->device->bios;
 	u16 table = init_io_condition_table(init);
 	if (table) {
 		u16 port = nvbios_rd16(bios, table + (cond * 5) + 0);
@@ -509,7 +509,7 @@ init_io_condition_met(struct nvbios_init *init, u8 cond)
 static bool
 init_io_flag_condition_met(struct nvbios_init *init, u8 cond)
 {
-	struct nvkm_bios *bios = init->bios;
+	struct nvkm_bios *bios = init->subdev->device->bios;
 	u16 table = init_io_flag_condition_table(init);
 	if (table) {
 		u16 port = nvbios_rd16(bios, table + (cond * 9) + 0);
@@ -580,7 +580,8 @@ init_tmds_reg(struct nvbios_init *init, u8 tmds)
 static void
 init_reserved(struct nvbios_init *init)
 {
-	u8 opcode = nvbios_rd08(init->bios, init->offset);
+	struct nvkm_bios *bios = init->subdev->device->bios;
+	u8 opcode = nvbios_rd08(bios, init->offset);
 	u8 length, i;
 
 	switch (opcode) {
@@ -594,7 +595,7 @@ init_reserved(struct nvbios_init *init)
 
 	trace("RESERVED 0x%02x\t", opcode);
 	for (i = 1; i < length; i++)
-		cont(" 0x%02x", nvbios_rd08(init->bios, init->offset + i));
+		cont(" 0x%02x", nvbios_rd08(bios, init->offset + i));
 	cont("\n");
 	init->offset += length;
 }
@@ -617,7 +618,7 @@ init_done(struct nvbios_init *init)
 static void
 init_io_restrict_prog(struct nvbios_init *init)
 {
-	struct nvkm_bios *bios = init->bios;
+	struct nvkm_bios *bios = init->subdev->device->bios;
 	u16 port = nvbios_rd16(bios, init->offset + 1);
 	u8 index = nvbios_rd08(bios, init->offset + 3);
 	u8  mask = nvbios_rd08(bios, init->offset + 4);
@@ -654,7 +655,7 @@ init_io_restrict_prog(struct nvbios_init *init)
 static void
 init_repeat(struct nvbios_init *init)
 {
-	struct nvkm_bios *bios = init->bios;
+	struct nvkm_bios *bios = init->subdev->device->bios;
 	u8 count = nvbios_rd08(bios, init->offset + 1);
 	u16 repeat = init->repeat;
 
@@ -680,7 +681,7 @@ init_repeat(struct nvbios_init *init)
 static void
 init_io_restrict_pll(struct nvbios_init *init)
 {
-	struct nvkm_bios *bios = init->bios;
+	struct nvkm_bios *bios = init->subdev->device->bios;
 	u16 port = nvbios_rd16(bios, init->offset + 1);
 	u8 index = nvbios_rd08(bios, init->offset + 3);
 	u8  mask = nvbios_rd08(bios, init->offset + 4);
@@ -736,7 +737,7 @@ init_end_repeat(struct nvbios_init *init)
 static void
 init_copy(struct nvbios_init *init)
 {
-	struct nvkm_bios *bios = init->bios;
+	struct nvkm_bios *bios = init->subdev->device->bios;
 	u32  reg = nvbios_rd32(bios, init->offset + 1);
 	u8 shift = nvbios_rd08(bios, init->offset + 5);
 	u8 smask = nvbios_rd08(bios, init->offset + 6);
@@ -775,7 +776,7 @@ init_not(struct nvbios_init *init)
 static void
 init_io_flag_condition(struct nvbios_init *init)
 {
-	struct nvkm_bios *bios = init->bios;
+	struct nvkm_bios *bios = init->subdev->device->bios;
 	u8 cond = nvbios_rd08(bios, init->offset + 1);
 
 	trace("IO_FLAG_CONDITION\t0x%02x\n", cond);
@@ -792,7 +793,7 @@ init_io_flag_condition(struct nvbios_init *init)
 static void
 init_generic_condition(struct nvbios_init *init)
 {
-	struct nvkm_bios *bios = init->bios;
+	struct nvkm_bios *bios = init->subdev->device->bios;
 	struct nvbios_dpout info;
 	u8  cond = nvbios_rd08(bios, init->offset + 1);
 	u8  size = nvbios_rd08(bios, init->offset + 2);
@@ -841,7 +842,7 @@ init_generic_condition(struct nvbios_init *init)
 static void
 init_io_mask_or(struct nvbios_init *init)
 {
-	struct nvkm_bios *bios = init->bios;
+	struct nvkm_bios *bios = init->subdev->device->bios;
 	u8 index = nvbios_rd08(bios, init->offset + 1);
 	u8    or = init_or(init);
 	u8  data;
@@ -860,7 +861,7 @@ init_io_mask_or(struct nvbios_init *init)
 static void
 init_io_or(struct nvbios_init *init)
 {
-	struct nvkm_bios *bios = init->bios;
+	struct nvkm_bios *bios = init->subdev->device->bios;
 	u8 index = nvbios_rd08(bios, init->offset + 1);
 	u8    or = init_or(init);
 	u8  data;
@@ -879,7 +880,7 @@ init_io_or(struct nvbios_init *init)
 static void
 init_andn_reg(struct nvbios_init *init)
 {
-	struct nvkm_bios *bios = init->bios;
+	struct nvkm_bios *bios = init->subdev->device->bios;
 	u32  reg = nvbios_rd32(bios, init->offset + 1);
 	u32 mask = nvbios_rd32(bios, init->offset + 5);
 
@@ -896,7 +897,7 @@ init_andn_reg(struct nvbios_init *init)
 static void
 init_or_reg(struct nvbios_init *init)
 {
-	struct nvkm_bios *bios = init->bios;
+	struct nvkm_bios *bios = init->subdev->device->bios;
 	u32  reg = nvbios_rd32(bios, init->offset + 1);
 	u32 mask = nvbios_rd32(bios, init->offset + 5);
 
@@ -913,7 +914,7 @@ init_or_reg(struct nvbios_init *init)
 static void
 init_idx_addr_latched(struct nvbios_init *init)
 {
-	struct nvkm_bios *bios = init->bios;
+	struct nvkm_bios *bios = init->subdev->device->bios;
 	u32 creg = nvbios_rd32(bios, init->offset + 1);
 	u32 dreg = nvbios_rd32(bios, init->offset + 5);
 	u32 mask = nvbios_rd32(bios, init->offset + 9);
@@ -943,7 +944,7 @@ init_idx_addr_latched(struct nvbios_init *init)
 static void
 init_io_restrict_pll2(struct nvbios_init *init)
 {
-	struct nvkm_bios *bios = init->bios;
+	struct nvkm_bios *bios = init->subdev->device->bios;
 	u16 port = nvbios_rd16(bios, init->offset + 1);
 	u8 index = nvbios_rd08(bios, init->offset + 3);
 	u8  mask = nvbios_rd08(bios, init->offset + 4);
@@ -978,7 +979,7 @@ init_io_restrict_pll2(struct nvbios_init *init)
 static void
 init_pll2(struct nvbios_init *init)
 {
-	struct nvkm_bios *bios = init->bios;
+	struct nvkm_bios *bios = init->subdev->device->bios;
 	u32  reg = nvbios_rd32(bios, init->offset + 1);
 	u32 freq = nvbios_rd32(bios, init->offset + 5);
 
@@ -995,7 +996,7 @@ init_pll2(struct nvbios_init *init)
 static void
 init_i2c_byte(struct nvbios_init *init)
 {
-	struct nvkm_bios *bios = init->bios;
+	struct nvkm_bios *bios = init->subdev->device->bios;
 	u8 index = nvbios_rd08(bios, init->offset + 1);
 	u8  addr = nvbios_rd08(bios, init->offset + 2) >> 1;
 	u8 count = nvbios_rd08(bios, init->offset + 3);
@@ -1026,7 +1027,7 @@ init_i2c_byte(struct nvbios_init *init)
 static void
 init_zm_i2c_byte(struct nvbios_init *init)
 {
-	struct nvkm_bios *bios = init->bios;
+	struct nvkm_bios *bios = init->subdev->device->bios;
 	u8 index = nvbios_rd08(bios, init->offset + 1);
 	u8  addr = nvbios_rd08(bios, init->offset + 2) >> 1;
 	u8 count = nvbios_rd08(bios, init->offset + 3);
@@ -1052,7 +1053,7 @@ init_zm_i2c_byte(struct nvbios_init *init)
 static void
 init_zm_i2c(struct nvbios_init *init)
 {
-	struct nvkm_bios *bios = init->bios;
+	struct nvkm_bios *bios = init->subdev->device->bios;
 	u8 index = nvbios_rd08(bios, init->offset + 1);
 	u8  addr = nvbios_rd08(bios, init->offset + 2) >> 1;
 	u8 count = nvbios_rd08(bios, init->offset + 3);
@@ -1086,7 +1087,7 @@ init_zm_i2c(struct nvbios_init *init)
 static void
 init_tmds(struct nvbios_init *init)
 {
-	struct nvkm_bios *bios = init->bios;
+	struct nvkm_bios *bios = init->subdev->device->bios;
 	u8 tmds = nvbios_rd08(bios, init->offset + 1);
 	u8 addr = nvbios_rd08(bios, init->offset + 2);
 	u8 mask = nvbios_rd08(bios, init->offset + 3);
@@ -1112,7 +1113,7 @@ init_tmds(struct nvbios_init *init)
 static void
 init_zm_tmds_group(struct nvbios_init *init)
 {
-	struct nvkm_bios *bios = init->bios;
+	struct nvkm_bios *bios = init->subdev->device->bios;
 	u8  tmds = nvbios_rd08(bios, init->offset + 1);
 	u8 count = nvbios_rd08(bios, init->offset + 2);
 	u32  reg = init_tmds_reg(init, tmds);
@@ -1139,7 +1140,7 @@ init_zm_tmds_group(struct nvbios_init *init)
 static void
 init_cr_idx_adr_latch(struct nvbios_init *init)
 {
-	struct nvkm_bios *bios = init->bios;
+	struct nvkm_bios *bios = init->subdev->device->bios;
 	u8 addr0 = nvbios_rd08(bios, init->offset + 1);
 	u8 addr1 = nvbios_rd08(bios, init->offset + 2);
 	u8  base = nvbios_rd08(bios, init->offset + 3);
@@ -1169,7 +1170,7 @@ init_cr_idx_adr_latch(struct nvbios_init *init)
 static void
 init_cr(struct nvbios_init *init)
 {
-	struct nvkm_bios *bios = init->bios;
+	struct nvkm_bios *bios = init->subdev->device->bios;
 	u8 addr = nvbios_rd08(bios, init->offset + 1);
 	u8 mask = nvbios_rd08(bios, init->offset + 2);
 	u8 data = nvbios_rd08(bios, init->offset + 3);
@@ -1189,7 +1190,7 @@ init_cr(struct nvbios_init *init)
 static void
 init_zm_cr(struct nvbios_init *init)
 {
-	struct nvkm_bios *bios = init->bios;
+	struct nvkm_bios *bios = init->subdev->device->bios;
 	u8 addr = nvbios_rd08(bios, init->offset + 1);
 	u8 data = nvbios_rd08(bios, init->offset + 2);
 
@@ -1206,7 +1207,7 @@ init_zm_cr(struct nvbios_init *init)
 static void
 init_zm_cr_group(struct nvbios_init *init)
 {
-	struct nvkm_bios *bios = init->bios;
+	struct nvkm_bios *bios = init->subdev->device->bios;
 	u8 count = nvbios_rd08(bios, init->offset + 1);
 
 	trace("ZM_CR_GROUP\n");
@@ -1230,7 +1231,7 @@ init_zm_cr_group(struct nvbios_init *init)
 static void
 init_condition_time(struct nvbios_init *init)
 {
-	struct nvkm_bios *bios = init->bios;
+	struct nvkm_bios *bios = init->subdev->device->bios;
 	u8  cond = nvbios_rd08(bios, init->offset + 1);
 	u8 retry = nvbios_rd08(bios, init->offset + 2);
 	u8  wait = min((u16)retry * 50, 100);
@@ -1257,7 +1258,7 @@ init_condition_time(struct nvbios_init *init)
 static void
 init_ltime(struct nvbios_init *init)
 {
-	struct nvkm_bios *bios = init->bios;
+	struct nvkm_bios *bios = init->subdev->device->bios;
 	u16 msec = nvbios_rd16(bios, init->offset + 1);
 
 	trace("LTIME\t0x%04x\n", msec);
@@ -1274,7 +1275,7 @@ init_ltime(struct nvbios_init *init)
 static void
 init_zm_reg_sequence(struct nvbios_init *init)
 {
-	struct nvkm_bios *bios = init->bios;
+	struct nvkm_bios *bios = init->subdev->device->bios;
 	u32 base = nvbios_rd32(bios, init->offset + 1);
 	u8 count = nvbios_rd08(bios, init->offset + 5);
 
@@ -1299,7 +1300,7 @@ init_zm_reg_sequence(struct nvbios_init *init)
 static void
 init_pll_indirect(struct nvbios_init *init)
 {
-	struct nvkm_bios *bios = init->bios;
+	struct nvkm_bios *bios = init->subdev->device->bios;
 	u32  reg = nvbios_rd32(bios, init->offset + 1);
 	u16 addr = nvbios_rd16(bios, init->offset + 5);
 	u32 freq = (u32)nvbios_rd16(bios, addr) * 1000;
@@ -1318,7 +1319,7 @@ init_pll_indirect(struct nvbios_init *init)
 static void
 init_zm_reg_indirect(struct nvbios_init *init)
 {
-	struct nvkm_bios *bios = init->bios;
+	struct nvkm_bios *bios = init->subdev->device->bios;
 	u32  reg = nvbios_rd32(bios, init->offset + 1);
 	u16 addr = nvbios_rd16(bios, init->offset + 5);
 	u32 data = nvbios_rd32(bios, addr);
@@ -1337,7 +1338,7 @@ init_zm_reg_indirect(struct nvbios_init *init)
 static void
 init_sub_direct(struct nvbios_init *init)
 {
-	struct nvkm_bios *bios = init->bios;
+	struct nvkm_bios *bios = init->subdev->device->bios;
 	u16 addr = nvbios_rd16(bios, init->offset + 1);
 	u16 save;
 
@@ -1363,7 +1364,7 @@ init_sub_direct(struct nvbios_init *init)
 static void
 init_jump(struct nvbios_init *init)
 {
-	struct nvkm_bios *bios = init->bios;
+	struct nvkm_bios *bios = init->subdev->device->bios;
 	u16 offset = nvbios_rd16(bios, init->offset + 1);
 
 	trace("JUMP\t0x%04x\n", offset);
@@ -1381,7 +1382,7 @@ init_jump(struct nvbios_init *init)
 static void
 init_i2c_if(struct nvbios_init *init)
 {
-	struct nvkm_bios *bios = init->bios;
+	struct nvkm_bios *bios = init->subdev->device->bios;
 	u8 index = nvbios_rd08(bios, init->offset + 1);
 	u8  addr = nvbios_rd08(bios, init->offset + 2);
 	u8   reg = nvbios_rd08(bios, init->offset + 3);
@@ -1408,7 +1409,7 @@ init_i2c_if(struct nvbios_init *init)
 static void
 init_copy_nv_reg(struct nvbios_init *init)
 {
-	struct nvkm_bios *bios = init->bios;
+	struct nvkm_bios *bios = init->subdev->device->bios;
 	u32  sreg = nvbios_rd32(bios, init->offset + 1);
 	u8  shift = nvbios_rd08(bios, init->offset + 5);
 	u32 smask = nvbios_rd32(bios, init->offset + 6);
@@ -1434,7 +1435,7 @@ init_copy_nv_reg(struct nvbios_init *init)
 static void
 init_zm_index_io(struct nvbios_init *init)
 {
-	struct nvkm_bios *bios = init->bios;
+	struct nvkm_bios *bios = init->subdev->device->bios;
 	u16 port = nvbios_rd16(bios, init->offset + 1);
 	u8 index = nvbios_rd08(bios, init->offset + 3);
 	u8  data = nvbios_rd08(bios, init->offset + 4);
@@ -1452,7 +1453,7 @@ init_zm_index_io(struct nvbios_init *init)
 static void
 init_compute_mem(struct nvbios_init *init)
 {
-	struct nvkm_devinit *devinit = init->bios->subdev.device->devinit;
+	struct nvkm_devinit *devinit = init->subdev->device->devinit;
 
 	trace("COMPUTE_MEM\n");
 	init->offset += 1;
@@ -1470,7 +1471,7 @@ init_compute_mem(struct nvbios_init *init)
 static void
 init_reset(struct nvbios_init *init)
 {
-	struct nvkm_bios *bios = init->bios;
+	struct nvkm_bios *bios = init->subdev->device->bios;
 	u32   reg = nvbios_rd32(bios, init->offset + 1);
 	u32 data1 = nvbios_rd32(bios, init->offset + 5);
 	u32 data2 = nvbios_rd32(bios, init->offset + 9);
@@ -1497,7 +1498,7 @@ init_reset(struct nvbios_init *init)
 static u16
 init_configure_mem_clk(struct nvbios_init *init)
 {
-	u16 mdata = bmp_mem_init_table(init->bios);
+	u16 mdata = bmp_mem_init_table(init->subdev->device->bios);
 	if (mdata)
 		mdata += (init_rdvgai(init, 0x03d4, 0x3c) >> 4) * 66;
 	return mdata;
@@ -1506,7 +1507,7 @@ init_configure_mem_clk(struct nvbios_init *init)
 static void
 init_configure_mem(struct nvbios_init *init)
 {
-	struct nvkm_bios *bios = init->bios;
+	struct nvkm_bios *bios = init->subdev->device->bios;
 	u16 mdata, sdata;
 	u32 addr, data;
 
@@ -1556,7 +1557,7 @@ init_configure_mem(struct nvbios_init *init)
 static void
 init_configure_clk(struct nvbios_init *init)
 {
-	struct nvkm_bios *bios = init->bios;
+	struct nvkm_bios *bios = init->subdev->device->bios;
 	u16 mdata, clock;
 
 	trace("CONFIGURE_CLK\n");
@@ -1590,7 +1591,7 @@ init_configure_clk(struct nvbios_init *init)
 static void
 init_configure_preinit(struct nvbios_init *init)
 {
-	struct nvkm_bios *bios = init->bios;
+	struct nvkm_bios *bios = init->subdev->device->bios;
 	u32 strap;
 
 	trace("CONFIGURE_PREINIT\n");
@@ -1616,7 +1617,7 @@ init_configure_preinit(struct nvbios_init *init)
 static void
 init_io(struct nvbios_init *init)
 {
-	struct nvkm_bios *bios = init->bios;
+	struct nvkm_bios *bios = init->subdev->device->bios;
 	u16 port = nvbios_rd16(bios, init->offset + 1);
 	u8  mask = nvbios_rd16(bios, init->offset + 3);
 	u8  data = nvbios_rd16(bios, init->offset + 4);
@@ -1656,7 +1657,7 @@ init_io(struct nvbios_init *init)
 static void
 init_sub(struct nvbios_init *init)
 {
-	struct nvkm_bios *bios = init->bios;
+	struct nvkm_bios *bios = init->subdev->device->bios;
 	u8 index = nvbios_rd08(bios, init->offset + 1);
 	u16 addr, save;
 
@@ -1683,7 +1684,7 @@ init_sub(struct nvbios_init *init)
 static void
 init_ram_condition(struct nvbios_init *init)
 {
-	struct nvkm_bios *bios = init->bios;
+	struct nvkm_bios *bios = init->subdev->device->bios;
 	u8  mask = nvbios_rd08(bios, init->offset + 1);
 	u8 value = nvbios_rd08(bios, init->offset + 2);
 
@@ -1702,7 +1703,7 @@ init_ram_condition(struct nvbios_init *init)
 static void
 init_nv_reg(struct nvbios_init *init)
 {
-	struct nvkm_bios *bios = init->bios;
+	struct nvkm_bios *bios = init->subdev->device->bios;
 	u32  reg = nvbios_rd32(bios, init->offset + 1);
 	u32 mask = nvbios_rd32(bios, init->offset + 5);
 	u32 data = nvbios_rd32(bios, init->offset + 9);
@@ -1720,7 +1721,7 @@ init_nv_reg(struct nvbios_init *init)
 static void
 init_macro(struct nvbios_init *init)
 {
-	struct nvkm_bios *bios = init->bios;
+	struct nvkm_bios *bios = init->subdev->device->bios;
 	u8  macro = nvbios_rd08(bios, init->offset + 1);
 	u16 table;
 
@@ -1756,7 +1757,7 @@ init_resume(struct nvbios_init *init)
 static void
 init_strap_condition(struct nvbios_init *init)
 {
-	struct nvkm_bios *bios = init->bios;
+	struct nvkm_bios *bios = init->subdev->device->bios;
 	u32 mask = nvbios_rd32(bios, init->offset + 1);
 	u32 value = nvbios_rd32(bios, init->offset + 5);
 
@@ -1774,7 +1775,7 @@ init_strap_condition(struct nvbios_init *init)
 static void
 init_time(struct nvbios_init *init)
 {
-	struct nvkm_bios *bios = init->bios;
+	struct nvkm_bios *bios = init->subdev->device->bios;
 	u16 usec = nvbios_rd16(bios, init->offset + 1);
 
 	trace("TIME\t0x%04x\n", usec);
@@ -1795,7 +1796,7 @@ init_time(struct nvbios_init *init)
 static void
 init_condition(struct nvbios_init *init)
 {
-	struct nvkm_bios *bios = init->bios;
+	struct nvkm_bios *bios = init->subdev->device->bios;
 	u8 cond = nvbios_rd08(bios, init->offset + 1);
 
 	trace("CONDITION\t0x%02x\n", cond);
@@ -1812,7 +1813,7 @@ init_condition(struct nvbios_init *init)
 static void
 init_io_condition(struct nvbios_init *init)
 {
-	struct nvkm_bios *bios = init->bios;
+	struct nvkm_bios *bios = init->subdev->device->bios;
 	u8 cond = nvbios_rd08(bios, init->offset + 1);
 
 	trace("IO_CONDITION\t0x%02x\n", cond);
@@ -1829,7 +1830,7 @@ init_io_condition(struct nvbios_init *init)
 static void
 init_zm_reg16(struct nvbios_init *init)
 {
-	struct nvkm_bios *bios = init->bios;
+	struct nvkm_bios *bios = init->subdev->device->bios;
 	u32 addr = nvbios_rd32(bios, init->offset + 1);
 	u16 data = nvbios_rd16(bios, init->offset + 5);
 
@@ -1846,7 +1847,7 @@ init_zm_reg16(struct nvbios_init *init)
 static void
 init_index_io(struct nvbios_init *init)
 {
-	struct nvkm_bios *bios = init->bios;
+	struct nvkm_bios *bios = init->subdev->device->bios;
 	u16 port = nvbios_rd16(bios, init->offset + 1);
 	u8 index = nvbios_rd16(bios, init->offset + 3);
 	u8  mask = nvbios_rd08(bios, init->offset + 4);
@@ -1868,7 +1869,7 @@ init_index_io(struct nvbios_init *init)
 static void
 init_pll(struct nvbios_init *init)
 {
-	struct nvkm_bios *bios = init->bios;
+	struct nvkm_bios *bios = init->subdev->device->bios;
 	u32  reg = nvbios_rd32(bios, init->offset + 1);
 	u32 freq = nvbios_rd16(bios, init->offset + 5) * 10;
 
@@ -1885,7 +1886,7 @@ init_pll(struct nvbios_init *init)
 static void
 init_zm_reg(struct nvbios_init *init)
 {
-	struct nvkm_bios *bios = init->bios;
+	struct nvkm_bios *bios = init->subdev->device->bios;
 	u32 addr = nvbios_rd32(bios, init->offset + 1);
 	u32 data = nvbios_rd32(bios, init->offset + 5);
 
@@ -1905,7 +1906,7 @@ init_zm_reg(struct nvbios_init *init)
 static void
 init_ram_restrict_pll(struct nvbios_init *init)
 {
-	struct nvkm_bios *bios = init->bios;
+	struct nvkm_bios *bios = init->subdev->device->bios;
 	u8  type = nvbios_rd08(bios, init->offset + 1);
 	u8 count = init_ram_restrict_group_count(init);
 	u8 strap = init_ram_restrict(init);
@@ -1935,7 +1936,7 @@ init_ram_restrict_pll(struct nvbios_init *init)
 static void
 init_gpio(struct nvbios_init *init)
 {
-	struct nvkm_gpio *gpio = init->bios->subdev.device->gpio;
+	struct nvkm_gpio *gpio = init->subdev->device->gpio;
 
 	trace("GPIO\n");
 	init->offset += 1;
@@ -1951,7 +1952,7 @@ init_gpio(struct nvbios_init *init)
 static void
 init_ram_restrict_zm_reg_group(struct nvbios_init *init)
 {
-	struct nvkm_bios *bios = init->bios;
+	struct nvkm_bios *bios = init->subdev->device->bios;
 	u32 addr = nvbios_rd32(bios, init->offset + 1);
 	u8  incr = nvbios_rd08(bios, init->offset + 5);
 	u8   num = nvbios_rd08(bios, init->offset + 6);
@@ -1989,7 +1990,7 @@ init_ram_restrict_zm_reg_group(struct nvbios_init *init)
 static void
 init_copy_zm_reg(struct nvbios_init *init)
 {
-	struct nvkm_bios *bios = init->bios;
+	struct nvkm_bios *bios = init->subdev->device->bios;
 	u32 sreg = nvbios_rd32(bios, init->offset + 1);
 	u32 dreg = nvbios_rd32(bios, init->offset + 5);
 
@@ -2006,7 +2007,7 @@ init_copy_zm_reg(struct nvbios_init *init)
 static void
 init_zm_reg_group(struct nvbios_init *init)
 {
-	struct nvkm_bios *bios = init->bios;
+	struct nvkm_bios *bios = init->subdev->device->bios;
 	u32 addr = nvbios_rd32(bios, init->offset + 1);
 	u8 count = nvbios_rd08(bios, init->offset + 5);
 
@@ -2028,7 +2029,7 @@ init_zm_reg_group(struct nvbios_init *init)
 static void
 init_xlat(struct nvbios_init *init)
 {
-	struct nvkm_bios *bios = init->bios;
+	struct nvkm_bios *bios = init->subdev->device->bios;
 	u32 saddr = nvbios_rd32(bios, init->offset + 1);
 	u8 sshift = nvbios_rd08(bios, init->offset + 5);
 	u8  smask = nvbios_rd08(bios, init->offset + 6);
@@ -2056,7 +2057,7 @@ init_xlat(struct nvbios_init *init)
 static void
 init_zm_mask_add(struct nvbios_init *init)
 {
-	struct nvkm_bios *bios = init->bios;
+	struct nvkm_bios *bios = init->subdev->device->bios;
 	u32 addr = nvbios_rd32(bios, init->offset + 1);
 	u32 mask = nvbios_rd32(bios, init->offset + 5);
 	u32  add = nvbios_rd32(bios, init->offset + 9);
@@ -2077,7 +2078,7 @@ init_zm_mask_add(struct nvbios_init *init)
 static void
 init_auxch(struct nvbios_init *init)
 {
-	struct nvkm_bios *bios = init->bios;
+	struct nvkm_bios *bios = init->subdev->device->bios;
 	u32 addr = nvbios_rd32(bios, init->offset + 1);
 	u8 count = nvbios_rd08(bios, init->offset + 5);
 
@@ -2101,7 +2102,7 @@ init_auxch(struct nvbios_init *init)
 static void
 init_zm_auxch(struct nvbios_init *init)
 {
-	struct nvkm_bios *bios = init->bios;
+	struct nvkm_bios *bios = init->subdev->device->bios;
 	u32 addr = nvbios_rd32(bios, init->offset + 1);
 	u8 count = nvbios_rd08(bios, init->offset + 5);
 
@@ -2123,7 +2124,7 @@ init_zm_auxch(struct nvbios_init *init)
 static void
 init_i2c_long_if(struct nvbios_init *init)
 {
-	struct nvkm_bios *bios = init->bios;
+	struct nvkm_bios *bios = init->subdev->device->bios;
 	u8 index = nvbios_rd08(bios, init->offset + 1);
 	u8  addr = nvbios_rd08(bios, init->offset + 2) >> 1;
 	u8 reglo = nvbios_rd08(bios, init->offset + 3);
@@ -2162,7 +2163,7 @@ init_i2c_long_if(struct nvbios_init *init)
 static void
 init_gpio_ne(struct nvbios_init *init)
 {
-	struct nvkm_bios *bios = init->bios;
+	struct nvkm_bios *bios = init->subdev->device->bios;
 	struct nvkm_gpio *gpio = bios->subdev.device->gpio;
 	struct dcb_gpio_func func;
 	u8 count = nvbios_rd08(bios, init->offset + 1);
@@ -2275,9 +2276,11 @@ static struct nvbios_init_opcode {
 int
 nvbios_exec(struct nvbios_init *init)
 {
+	struct nvkm_bios *bios = init->subdev->device->bios;
+
 	init->nested++;
 	while (init->offset) {
-		u8 opcode = nvbios_rd08(init->bios, init->offset);
+		u8 opcode = nvbios_rd08(bios, init->offset);
 		if (opcode >= init_opcode_nr || !init_opcode[opcode].exec) {
 			error("unknown opcode 0x%02x\n", opcode);
 			return -EINVAL;
@@ -2290,7 +2293,7 @@ nvbios_exec(struct nvbios_init *init)
 }
 
 int
-nvbios_init(struct nvkm_subdev *subdev, bool execute)
+nvbios_post(struct nvkm_subdev *subdev, bool execute)
 {
 	struct nvkm_bios *bios = subdev->device->bios;
 	int ret = 0;
@@ -2300,32 +2303,18 @@ nvbios_init(struct nvkm_subdev *subdev, bool execute)
 	if (execute)
 		nvkm_debug(subdev, "running init tables\n");
 	while (!ret && (data = (init_script(bios, ++i)))) {
-		struct nvbios_init init = {
-			.subdev = subdev,
-			.bios = bios,
-			.offset = data,
-			.outp = NULL,
-			.crtc = -1,
-			.execute = execute ? 1 : 0,
-		};
-
-		ret = nvbios_exec(&init);
+		ret = nvbios_init(subdev, data,
+			init.execute = execute ? 1 : 0;
+		      );
 	}
 
 	/* the vbios parser will run this right after the normal init
 	 * tables, whereas the binary driver appears to run it later.
 	 */
 	if (!ret && (data = init_unknown_script(bios))) {
-		struct nvbios_init init = {
-			.subdev = subdev,
-			.bios = bios,
-			.offset = data,
-			.outp = NULL,
-			.crtc = -1,
-			.execute = execute ? 1 : 0,
-		};
-
-		ret = nvbios_exec(&init);
+		ret = nvbios_init(subdev, data,
+			init.execute = execute ? 1 : 0;
+		      );
 	}
 
 	return ret;
diff --git a/drivers/gpu/drm/nouveau/nvkm/subdev/bios/volt.c b/drivers/gpu/drm/nouveau/nvkm/subdev/bios/volt.c
index a7797a9e9cbc..7143ea4611aa 100644
--- a/drivers/gpu/drm/nouveau/nvkm/subdev/bios/volt.c
+++ b/drivers/gpu/drm/nouveau/nvkm/subdev/bios/volt.c
@@ -93,9 +93,9 @@ nvbios_volt_parse(struct nvkm_bios *bios, u8 *ver, u8 *hdr, u8 *cnt, u8 *len,
 		info->step    = nvbios_rd16(bios, volt + 0x08);
 		info->vidmask = nvbios_rd08(bios, volt + 0x0b);
 		info->ranged  = true; /* XXX: find the flag byte */
-		/*XXX*/
-		info->min     = 0;
-		info->max     = info->base;
+		info->min     = min(info->base,
+				    info->base + info->step * info->vidmask);
+		info->max     = nvbios_rd32(bios, volt + 0x0e);
 		break;
 	case 0x50:
 		info->min     = nvbios_rd32(bios, volt + 0x0a);
diff --git a/drivers/gpu/drm/nouveau/nvkm/subdev/devinit/nv04.c b/drivers/gpu/drm/nouveau/nvkm/subdev/devinit/nv04.c
index c8d455346fcd..158977f8a6e6 100644
--- a/drivers/gpu/drm/nouveau/nvkm/subdev/devinit/nv04.c
+++ b/drivers/gpu/drm/nouveau/nvkm/subdev/devinit/nv04.c
@@ -393,7 +393,7 @@ nv04_devinit_pll_set(struct nvkm_devinit *devinit, u32 type, u32 freq)
 int
 nv04_devinit_post(struct nvkm_devinit *init, bool execute)
 {
-	return nvbios_init(&init->subdev, execute);
+	return nvbios_post(&init->subdev, execute);
 }
 
 void
diff --git a/drivers/gpu/drm/nouveau/nvkm/subdev/devinit/nv50.c b/drivers/gpu/drm/nouveau/nvkm/subdev/devinit/nv50.c
index 59362f8dee22..d7947c4391dc 100644
--- a/drivers/gpu/drm/nouveau/nvkm/subdev/devinit/nv50.c
+++ b/drivers/gpu/drm/nouveau/nvkm/subdev/devinit/nv50.c
@@ -137,16 +137,11 @@ nv50_devinit_init(struct nvkm_devinit *base)
 	while (init->base.post && dcb_outp_parse(bios, i, &ver, &hdr, &outp)) {
 		if (nvbios_outp_match(bios, outp.hasht, outp.hashm,
 				      &ver, &hdr, &cnt, &len, &info)) {
-			struct nvbios_init exec = {
-				.subdev = subdev,
-				.bios = bios,
-				.offset = info.script[0],
-				.outp = &outp,
-				.crtc = -1,
-				.execute = 1,
-			};
-
-			nvbios_exec(&exec);
+			nvbios_init(subdev, info.script[0],
+				init.outp = &outp;
+				init.or   = ffs(outp.or) - 1;
+				init.link = outp.sorconf.link == 2;
+			);
 		}
 		i++;
 	}
diff --git a/drivers/gpu/drm/nouveau/nvkm/subdev/fb/ramgk104.c b/drivers/gpu/drm/nouveau/nvkm/subdev/fb/ramgk104.c
index f6c00791722c..75814f15eb53 100644
--- a/drivers/gpu/drm/nouveau/nvkm/subdev/fb/ramgk104.c
+++ b/drivers/gpu/drm/nouveau/nvkm/subdev/fb/ramgk104.c
@@ -1424,12 +1424,7 @@ gk104_ram_init(struct nvkm_ram *ram)
 	for (i = 0; i < cnt; i++, data += 4) {
 		if (i != save >> 4) {
 			nvkm_mask(device, 0x10f65c, 0x000000f0, i << 4);
-			nvbios_exec(&(struct nvbios_init) {
-					.subdev = subdev,
-					.bios = bios,
-					.offset = nvbios_rd32(bios, data),
-					.execute = 1,
-				    });
+			nvbios_init(subdev, nvbios_rd32(bios, data));
 		}
 	}
 	nvkm_mask(device, 0x10f65c, 0x000000f0, save);
diff --git a/drivers/gpu/drm/nouveau/nvkm/subdev/fb/ramgp100.c b/drivers/gpu/drm/nouveau/nvkm/subdev/fb/ramgp100.c
index cac70047ad5a..df8a87333b67 100644
--- a/drivers/gpu/drm/nouveau/nvkm/subdev/fb/ramgp100.c
+++ b/drivers/gpu/drm/nouveau/nvkm/subdev/fb/ramgp100.c
@@ -59,12 +59,7 @@ gp100_ram_init(struct nvkm_ram *ram)
 		for (i = 0; i < cnt; i++, data += 4) {
 			if (i != save >> 4) {
 				nvkm_mask(device, 0x9a065c, 0x000000f0, i << 4);
-				nvbios_exec(&(struct nvbios_init) {
-						.subdev = subdev,
-						.bios = bios,
-						.offset = nvbios_rd32(bios, data),
-						.execute = 1,
-					    });
+				nvbios_init(subdev, nvbios_rd32(bios, data));
 			}
 		}
 		nvkm_mask(device, 0x9a065c, 0x000000f0, save);
diff --git a/drivers/gpu/drm/nouveau/nvkm/subdev/fb/ramnv40.c b/drivers/gpu/drm/nouveau/nvkm/subdev/fb/ramnv40.c
index 56f8cffc2560..70c63535d56b 100644
--- a/drivers/gpu/drm/nouveau/nvkm/subdev/fb/ramnv40.c
+++ b/drivers/gpu/drm/nouveau/nvkm/subdev/fb/ramnv40.c
@@ -150,16 +150,8 @@ nv40_ram_prog(struct nvkm_ram *base)
 	udelay(100);
 
 	/* execute memory reset script from vbios */
-	if (!bit_entry(bios, 'M', &M)) {
-		struct nvbios_init init = {
-			.subdev = subdev,
-			.bios = bios,
-			.offset = nvbios_rd16(bios, M.offset + 0x00),
-			.execute = 1,
-		};
-
-		nvbios_exec(&init);
-	}
+	if (!bit_entry(bios, 'M', &M))
+		nvbios_init(subdev, nvbios_rd16(bios, M.offset + 0x00));
 
 	/* make sure we're in vblank (hopefully the same one as before), and
 	 * then re-enable crtc memory access
diff --git a/drivers/gpu/drm/nouveau/nvkm/subdev/pmu/gk20a.c b/drivers/gpu/drm/nouveau/nvkm/subdev/pmu/gk20a.c
index 9ca0db796cbe..978aae3c1001 100644
--- a/drivers/gpu/drm/nouveau/nvkm/subdev/pmu/gk20a.c
+++ b/drivers/gpu/drm/nouveau/nvkm/subdev/pmu/gk20a.c
@@ -158,7 +158,7 @@ static void
 gk20a_pmu_fini(struct nvkm_pmu *pmu)
 {
 	struct gk20a_pmu *gpmu = gk20a_pmu(pmu);
-	nvkm_timer_alarm_cancel(pmu->subdev.device->timer, &gpmu->alarm);
+	nvkm_timer_alarm(pmu->subdev.device->timer, 0, &gpmu->alarm);
 
 	nvkm_falcon_put(pmu->falcon, &pmu->subdev);
 }
diff --git a/drivers/gpu/drm/nouveau/nvkm/subdev/therm/base.c b/drivers/gpu/drm/nouveau/nvkm/subdev/therm/base.c
index be691a7b972f..952a7cb0a59a 100644
--- a/drivers/gpu/drm/nouveau/nvkm/subdev/therm/base.c
+++ b/drivers/gpu/drm/nouveau/nvkm/subdev/therm/base.c
@@ -116,7 +116,7 @@ nvkm_therm_update(struct nvkm_therm *therm, int mode)
 
 	switch (mode) {
 	case NVKM_THERM_CTRL_MANUAL:
-		nvkm_timer_alarm_cancel(tmr, &therm->alarm);
+		nvkm_timer_alarm(tmr, 0, &therm->alarm);
 		duty = nvkm_therm_fan_get(therm);
 		if (duty < 0)
 			duty = 100;
@@ -142,7 +142,7 @@ nvkm_therm_update(struct nvkm_therm *therm, int mode)
 		break;
 	case NVKM_THERM_CTRL_NONE:
 	default:
-		nvkm_timer_alarm_cancel(tmr, &therm->alarm);
+		nvkm_timer_alarm(tmr, 0, &therm->alarm);
 		poll = false;
 	}
 
diff --git a/drivers/gpu/drm/nouveau/nvkm/subdev/therm/fan.c b/drivers/gpu/drm/nouveau/nvkm/subdev/therm/fan.c
index e2feccec25f5..f8fa43c8a7d2 100644
--- a/drivers/gpu/drm/nouveau/nvkm/subdev/therm/fan.c
+++ b/drivers/gpu/drm/nouveau/nvkm/subdev/therm/fan.c
@@ -215,7 +215,7 @@ nvkm_therm_fan_fini(struct nvkm_therm *therm, bool suspend)
 {
 	struct nvkm_timer *tmr = therm->subdev.device->timer;
 	if (suspend)
-		nvkm_timer_alarm_cancel(tmr, &therm->fan->alarm);
+		nvkm_timer_alarm(tmr, 0, &therm->fan->alarm);
 	return 0;
 }
 
diff --git a/drivers/gpu/drm/nouveau/nvkm/subdev/therm/temp.c b/drivers/gpu/drm/nouveau/nvkm/subdev/therm/temp.c
index 9a79e91fdfdc..e93b2410c38b 100644
--- a/drivers/gpu/drm/nouveau/nvkm/subdev/therm/temp.c
+++ b/drivers/gpu/drm/nouveau/nvkm/subdev/therm/temp.c
@@ -220,7 +220,7 @@ nvkm_therm_sensor_fini(struct nvkm_therm *therm, bool suspend)
 {
 	struct nvkm_timer *tmr = therm->subdev.device->timer;
 	if (suspend)
-		nvkm_timer_alarm_cancel(tmr, &therm->sensor.therm_poll_alarm);
+		nvkm_timer_alarm(tmr, 0, &therm->sensor.therm_poll_alarm);
 	return 0;
 }
 
diff --git a/drivers/gpu/drm/nouveau/nvkm/subdev/timer/base.c b/drivers/gpu/drm/nouveau/nvkm/subdev/timer/base.c
index 2437f7d41ca2..36de23d12ae4 100644
--- a/drivers/gpu/drm/nouveau/nvkm/subdev/timer/base.c
+++ b/drivers/gpu/drm/nouveau/nvkm/subdev/timer/base.c
@@ -105,15 +105,6 @@ nvkm_timer_alarm(struct nvkm_timer *tmr, u32 nsec, struct nvkm_alarm *alarm)
 	spin_unlock_irqrestore(&tmr->lock, flags);
 }
 
-void
-nvkm_timer_alarm_cancel(struct nvkm_timer *tmr, struct nvkm_alarm *alarm)
-{
-	unsigned long flags;
-	spin_lock_irqsave(&tmr->lock, flags);
-	list_del_init(&alarm->head);
-	spin_unlock_irqrestore(&tmr->lock, flags);
-}
-
 static void
 nvkm_timer_intr(struct nvkm_subdev *subdev)
 {