drm/nouveau/core: rework event interface

This is a lot of prep-work for being able to send event notifications
back to userspace.  Events now contain data, rather than a "something
just happened" signal.

Handler data is now embedded into a containing structure, rather than
being kmalloc()'d, and can optionally have the notify routine handled
in a workqueue.

Various races between suspend/unload with display HPD/DP IRQ handlers
automagically solved as a result.

Signed-off-by: Ben Skeggs <bskeggs@redhat.com>
diff --git a/drivers/gpu/drm/nouveau/Makefile b/drivers/gpu/drm/nouveau/Makefile
index fcd915c..749a047 100644
--- a/drivers/gpu/drm/nouveau/Makefile
+++ b/drivers/gpu/drm/nouveau/Makefile
@@ -16,6 +16,7 @@
 nouveau-y += core/core/handle.o
 nouveau-y += core/core/mm.o
 nouveau-y += core/core/namedb.o
+nouveau-y += core/core/notify.o
 nouveau-y += core/core/object.o
 nouveau-y += core/core/option.o
 nouveau-y += core/core/parent.o
diff --git a/drivers/gpu/drm/nouveau/core/core/event.c b/drivers/gpu/drm/nouveau/core/core/event.c
index ae81d3b..0540a48 100644
--- a/drivers/gpu/drm/nouveau/core/core/event.c
+++ b/drivers/gpu/drm/nouveau/core/core/event.c
@@ -1,5 +1,5 @@
 /*
- * Copyright 2013 Red Hat Inc.
+ * Copyright 2013-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"),
@@ -24,173 +24,77 @@
 #include <core/event.h>
 
 void
-nouveau_event_put(struct nouveau_eventh *handler)
+nvkm_event_put(struct nvkm_event *event, u32 types, int index)
 {
-	struct nouveau_event *event = handler->event;
-	unsigned long flags;
-	u32 m, t;
-
-	if (!__test_and_clear_bit(NVKM_EVENT_ENABLE, &handler->flags))
-		return;
-
-	spin_lock_irqsave(&event->refs_lock, flags);
-	for (m = handler->types; t = __ffs(m), m; m &= ~(1 << t)) {
-		if (!--event->refs[handler->index * event->types_nr + t]) {
-			if (event->disable)
-				event->disable(event, 1 << t, handler->index);
+	BUG_ON(!spin_is_locked(&event->refs_lock));
+	while (types) {
+		int type = __ffs(types); types &= ~(1 << type);
+		if (--event->refs[index * event->types_nr + type] == 0) {
+			if (event->func->fini)
+				event->func->fini(event, 1 << type, index);
 		}
-
 	}
-	spin_unlock_irqrestore(&event->refs_lock, flags);
 }
 
 void
-nouveau_event_get(struct nouveau_eventh *handler)
+nvkm_event_get(struct nvkm_event *event, u32 types, int index)
 {
-	struct nouveau_event *event = handler->event;
-	unsigned long flags;
-	u32 m, t;
-
-	if (__test_and_set_bit(NVKM_EVENT_ENABLE, &handler->flags))
-		return;
-
-	spin_lock_irqsave(&event->refs_lock, flags);
-	for (m = handler->types; t = __ffs(m), m; m &= ~(1 << t)) {
-		if (!event->refs[handler->index * event->types_nr + t]++) {
-			if (event->enable)
-				event->enable(event, 1 << t, handler->index);
+	BUG_ON(!spin_is_locked(&event->refs_lock));
+	while (types) {
+		int type = __ffs(types); types &= ~(1 << type);
+		if (++event->refs[index * event->types_nr + type] == 1) {
+			if (event->func->init)
+				event->func->init(event, 1 << type, index);
 		}
-
 	}
-	spin_unlock_irqrestore(&event->refs_lock, flags);
-}
-
-static void
-nouveau_event_fini(struct nouveau_eventh *handler)
-{
-	struct nouveau_event *event = handler->event;
-	unsigned long flags;
-	nouveau_event_put(handler);
-	spin_lock_irqsave(&event->list_lock, flags);
-	list_del(&handler->head);
-	spin_unlock_irqrestore(&event->list_lock, flags);
-}
-
-static int
-nouveau_event_init(struct nouveau_event *event, u32 types, int index,
-		   int (*func)(void *, u32, int), void *priv,
-		   struct nouveau_eventh *handler)
-{
-	unsigned long flags;
-
-	if (types & ~((1 << event->types_nr) - 1))
-		return -EINVAL;
-	if (index >= event->index_nr)
-		return -EINVAL;
-
-	handler->event = event;
-	handler->flags = 0;
-	handler->types = types;
-	handler->index = index;
-	handler->func = func;
-	handler->priv = priv;
-
-	spin_lock_irqsave(&event->list_lock, flags);
-	list_add_tail(&handler->head, &event->list[index]);
-	spin_unlock_irqrestore(&event->list_lock, flags);
-	return 0;
-}
-
-int
-nouveau_event_new(struct nouveau_event *event, u32 types, int index,
-		  int (*func)(void *, u32, int), void *priv,
-		  struct nouveau_eventh **phandler)
-{
-	struct nouveau_eventh *handler;
-	int ret = -ENOMEM;
-
-	if (event->check) {
-		ret = event->check(event, types, index);
-		if (ret)
-			return ret;
-	}
-
-	handler = *phandler = kmalloc(sizeof(*handler), GFP_KERNEL);
-	if (handler) {
-		ret = nouveau_event_init(event, types, index, func, priv, handler);
-		if (ret)
-			kfree(handler);
-	}
-
-	return ret;
 }
 
 void
-nouveau_event_ref(struct nouveau_eventh *handler, struct nouveau_eventh **ref)
+nvkm_event_send(struct nvkm_event *event, u32 types, int index,
+		void *data, u32 size)
 {
-	BUG_ON(handler != NULL);
-	if (*ref) {
-		nouveau_event_fini(*ref);
-		kfree(*ref);
-	}
-	*ref = handler;
-}
-
-void
-nouveau_event_trigger(struct nouveau_event *event, u32 types, int index)
-{
-	struct nouveau_eventh *handler;
+	struct nvkm_notify *notify;
 	unsigned long flags;
 
-	if (WARN_ON(index >= event->index_nr))
+	if (!event->refs || WARN_ON(index >= event->index_nr))
 		return;
 
 	spin_lock_irqsave(&event->list_lock, flags);
-	list_for_each_entry(handler, &event->list[index], head) {
-		if (!test_bit(NVKM_EVENT_ENABLE, &handler->flags))
-			continue;
-		if (!(handler->types & types))
-			continue;
-		if (handler->func(handler->priv, handler->types & types, index)
-				!= NVKM_EVENT_DROP)
-			continue;
-		nouveau_event_put(handler);
+	list_for_each_entry(notify, &event->list, head) {
+		if (notify->index == index && (notify->types & types)) {
+			if (event->func->send) {
+				event->func->send(data, size, notify);
+				continue;
+			}
+			nvkm_notify_send(notify, data, size);
+		}
 	}
 	spin_unlock_irqrestore(&event->list_lock, flags);
 }
 
 void
-nouveau_event_destroy(struct nouveau_event **pevent)
+nvkm_event_fini(struct nvkm_event *event)
 {
-	struct nouveau_event *event = *pevent;
-	if (event) {
-		kfree(event);
-		*pevent = NULL;
+	if (event->refs) {
+		kfree(event->refs);
+		event->refs = NULL;
 	}
 }
 
 int
-nouveau_event_create(int types_nr, int index_nr, struct nouveau_event **pevent)
+nvkm_event_init(const struct nvkm_event_func *func, int types_nr, int index_nr,
+		struct nvkm_event *event)
 {
-	struct nouveau_event *event;
-	int i;
-
-	event = *pevent = kzalloc(sizeof(*event) + (index_nr * types_nr) *
-				  sizeof(event->refs[0]), GFP_KERNEL);
-	if (!event)
+	event->refs = kzalloc(sizeof(*event->refs) * index_nr * types_nr,
+			      GFP_KERNEL);
+	if (!event->refs)
 		return -ENOMEM;
 
-	event->list = kmalloc(sizeof(*event->list) * index_nr, GFP_KERNEL);
-	if (!event->list) {
-		kfree(event);
-		return -ENOMEM;
-	}
-
-	spin_lock_init(&event->list_lock);
-	spin_lock_init(&event->refs_lock);
-	for (i = 0; i < index_nr; i++)
-		INIT_LIST_HEAD(&event->list[i]);
+	event->func = func;
 	event->types_nr = types_nr;
 	event->index_nr = index_nr;
+	spin_lock_init(&event->refs_lock);
+	spin_lock_init(&event->list_lock);
+	INIT_LIST_HEAD(&event->list);
 	return 0;
 }
diff --git a/drivers/gpu/drm/nouveau/core/core/notify.c b/drivers/gpu/drm/nouveau/core/core/notify.c
new file mode 100644
index 0000000..460f4ec
--- /dev/null
+++ b/drivers/gpu/drm/nouveau/core/core/notify.c
@@ -0,0 +1,164 @@
+/*
+ * 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 <bskeggs@redhat.com>
+ */
+
+#include <core/os.h>
+#include <core/event.h>
+#include <core/notify.h>
+
+static inline void
+nvkm_notify_put_locked(struct nvkm_notify *notify)
+{
+	if (notify->block++ == 0)
+		nvkm_event_put(notify->event, notify->types, notify->index);
+}
+
+void
+nvkm_notify_put(struct nvkm_notify *notify)
+{
+	struct nvkm_event *event = notify->event;
+	unsigned long flags;
+	if (likely(event) &&
+	    test_and_clear_bit(NVKM_NOTIFY_USER, &notify->flags)) {
+		spin_lock_irqsave(&event->refs_lock, flags);
+		nvkm_notify_put_locked(notify);
+		spin_unlock_irqrestore(&event->refs_lock, flags);
+		if (test_bit(NVKM_NOTIFY_WORK, &notify->flags))
+			flush_work(&notify->work);
+	}
+}
+
+static inline void
+nvkm_notify_get_locked(struct nvkm_notify *notify)
+{
+	if (--notify->block == 0)
+		nvkm_event_get(notify->event, notify->types, notify->index);
+}
+
+void
+nvkm_notify_get(struct nvkm_notify *notify)
+{
+	struct nvkm_event *event = notify->event;
+	unsigned long flags;
+	if (likely(event) &&
+	    !test_and_set_bit(NVKM_NOTIFY_USER, &notify->flags)) {
+		spin_lock_irqsave(&event->refs_lock, flags);
+		nvkm_notify_get_locked(notify);
+		spin_unlock_irqrestore(&event->refs_lock, flags);
+	}
+}
+
+static inline void
+nvkm_notify_func(struct nvkm_notify *notify)
+{
+	struct nvkm_event *event = notify->event;
+	int ret = notify->func(notify);
+	unsigned long flags;
+	if ((ret == NVKM_NOTIFY_KEEP) ||
+	    !test_and_clear_bit(NVKM_NOTIFY_USER, &notify->flags)) {
+		spin_lock_irqsave(&event->refs_lock, flags);
+		nvkm_notify_get_locked(notify);
+		spin_unlock_irqrestore(&event->refs_lock, flags);
+	}
+}
+
+static void
+nvkm_notify_work(struct work_struct *work)
+{
+	struct nvkm_notify *notify = container_of(work, typeof(*notify), work);
+	nvkm_notify_func(notify);
+}
+
+void
+nvkm_notify_send(struct nvkm_notify *notify, void *data, u32 size)
+{
+	struct nvkm_event *event = notify->event;
+	unsigned long flags;
+
+	BUG_ON(!spin_is_locked(&event->list_lock));
+	BUG_ON(size != notify->size);
+
+	spin_lock_irqsave(&event->refs_lock, flags);
+	if (notify->block) {
+		spin_unlock_irqrestore(&event->refs_lock, flags);
+		return;
+	}
+	nvkm_notify_put_locked(notify);
+	spin_unlock_irqrestore(&event->refs_lock, flags);
+
+	if (test_bit(NVKM_NOTIFY_WORK, &notify->flags)) {
+		memcpy((void *)notify->data, data, size);
+		schedule_work(&notify->work);
+	} else {
+		notify->data = data;
+		nvkm_notify_func(notify);
+		notify->data = NULL;
+	}
+}
+
+void
+nvkm_notify_fini(struct nvkm_notify *notify)
+{
+	unsigned long flags;
+	if (notify->event) {
+		nvkm_notify_put(notify);
+		spin_lock_irqsave(&notify->event->list_lock, flags);
+		list_del(&notify->head);
+		spin_unlock_irqrestore(&notify->event->list_lock, flags);
+		kfree((void *)notify->data);
+		notify->event = NULL;
+	}
+}
+
+int
+nvkm_notify_init(struct nvkm_event *event, int (*func)(struct nvkm_notify *),
+		 bool work, void *data, u32 size, u32 reply,
+		 struct nvkm_notify *notify)
+{
+	unsigned long flags;
+	int ret = -ENODEV;
+	if ((notify->event = event), event->refs) {
+		ret = event->func->ctor(data, size, notify);
+		if (ret == 0 && (ret = -EINVAL, notify->size == reply)) {
+			notify->flags = 0;
+			notify->block = 1;
+			notify->func = func;
+			notify->data = NULL;
+			if (ret = 0, work) {
+				INIT_WORK(&notify->work, nvkm_notify_work);
+				set_bit(NVKM_NOTIFY_WORK, &notify->flags);
+				notify->data = kmalloc(reply, GFP_KERNEL);
+				if (!notify->data)
+					ret = -ENOMEM;
+			}
+		}
+		if (ret == 0) {
+			spin_lock_irqsave(&event->list_lock, flags);
+			list_add_tail(&notify->head, &event->list);
+			spin_unlock_irqrestore(&event->list_lock, flags);
+		}
+	}
+	if (ret)
+		notify->event = NULL;
+	return ret;
+}
diff --git a/drivers/gpu/drm/nouveau/core/engine/device/acpi.c b/drivers/gpu/drm/nouveau/core/engine/device/acpi.c
index 79a2e4d..4dbf0ba 100644
--- a/drivers/gpu/drm/nouveau/core/engine/device/acpi.c
+++ b/drivers/gpu/drm/nouveau/core/engine/device/acpi.c
@@ -33,7 +33,7 @@
 	struct acpi_bus_event *info = data;
 
 	if (!strcmp(info->device_class, "ac_adapter"))
-		nouveau_event_trigger(device->ntfy, 1, NVKM_DEVICE_NTFY_POWER);
+		nvkm_event_send(&device->event, 1, 0, NULL, 0);
 
 	return NOTIFY_DONE;
 }
diff --git a/drivers/gpu/drm/nouveau/core/engine/device/base.c b/drivers/gpu/drm/nouveau/core/engine/device/base.c
index 6c16dab..ac31a6d 100644
--- a/drivers/gpu/drm/nouveau/core/engine/device/base.c
+++ b/drivers/gpu/drm/nouveau/core/engine/device/base.c
@@ -364,6 +364,7 @@
 /******************************************************************************
  * nouveau_device: engine functions
  *****************************************************************************/
+
 static struct nouveau_oclass
 nouveau_device_sclass[] = {
 	{ 0x0080, &nouveau_devobj_ofuncs },
@@ -371,6 +372,23 @@
 };
 
 static int
+nouveau_device_event_ctor(void *data, u32 size, struct nvkm_notify *notify)
+{
+	if (!WARN_ON(size != 0)) {
+		notify->size  = 0;
+		notify->types = 1;
+		notify->index = 0;
+		return 0;
+	}
+	return -EINVAL;
+}
+
+static const struct nvkm_event_func
+nouveau_device_event_func = {
+	.ctor = nouveau_device_event_ctor,
+};
+
+static int
 nouveau_device_fini(struct nouveau_object *object, bool suspend)
 {
 	struct nouveau_device *device = (void *)object;
@@ -445,7 +463,7 @@
 {
 	struct nouveau_device *device = (void *)object;
 
-	nouveau_event_destroy(&device->ntfy);
+	nvkm_event_fini(&device->event);
 
 	mutex_lock(&nv_devices_mutex);
 	list_del(&device->head);
@@ -545,7 +563,8 @@
 	nv_engine(device)->sclass = nouveau_device_sclass;
 	list_add(&device->head, &nv_devices);
 
-	ret = nouveau_event_create(1, NVKM_DEVICE_NTFY, &device->ntfy);
+	ret = nvkm_event_init(&nouveau_device_event_func, 1, 1,
+			      &device->event);
 done:
 	mutex_unlock(&nv_devices_mutex);
 	return ret;
diff --git a/drivers/gpu/drm/nouveau/core/engine/disp/base.c b/drivers/gpu/drm/nouveau/core/engine/disp/base.c
index 9c38c5e..d1df0ce 100644
--- a/drivers/gpu/drm/nouveau/core/engine/disp/base.c
+++ b/drivers/gpu/drm/nouveau/core/engine/disp/base.c
@@ -22,25 +22,76 @@
  * Authors: Ben Skeggs
  */
 
+#include <core/os.h>
+#include <nvif/unpack.h>
+#include <nvif/event.h>
+
 #include "priv.h"
 #include "outp.h"
 #include "conn.h"
 
-static int
-nouveau_disp_hpd_check(struct nouveau_event *event, u32 types, int index)
+int
+nouveau_disp_vblank_ctor(void *data, u32 size, struct nvkm_notify *notify)
 {
-	struct nouveau_disp *disp = event->priv;
-	struct nvkm_output *outp;
-	list_for_each_entry(outp, &disp->outp, head) {
-		if (outp->conn->index == index) {
-			if (outp->conn->hpd.event)
-				return 0;
-			break;
+	struct nouveau_disp *disp =
+		container_of(notify->event, typeof(*disp), vblank);
+	union {
+		struct nvif_notify_head_req_v0 v0;
+	} *req = data;
+	int ret;
+
+	if (nvif_unpack(req->v0, 0, 0, false)) {
+		notify->size = sizeof(struct nvif_notify_head_rep_v0);
+		if (ret = -ENXIO, req->v0.head <= disp->vblank.index_nr) {
+			notify->types = 1;
+			notify->index = req->v0.head;
+			return 0;
 		}
 	}
-	return -ENOSYS;
+
+	return ret;
 }
 
+void
+nouveau_disp_vblank(struct nouveau_disp *disp, int head)
+{
+	struct nvif_notify_head_rep_v0 rep = {};
+	nvkm_event_send(&disp->vblank, 1, head, &rep, sizeof(rep));
+}
+
+static int
+nouveau_disp_hpd_ctor(void *data, u32 size, struct nvkm_notify *notify)
+{
+	struct nouveau_disp *disp =
+		container_of(notify->event, typeof(*disp), hpd);
+	union {
+		struct nvif_notify_conn_req_v0 v0;
+	} *req = data;
+	struct nvkm_output *outp;
+	int ret;
+
+	if (nvif_unpack(req->v0, 0, 0, false)) {
+		notify->size = sizeof(struct nvif_notify_conn_rep_v0);
+		list_for_each_entry(outp, &disp->outp, head) {
+			if (ret = -ENXIO, outp->conn->index == req->v0.conn) {
+				if (ret = -ENODEV, outp->conn->hpd.event) {
+					notify->types = req->v0.mask;
+					notify->index = req->v0.conn;
+					ret = 0;
+				}
+				break;
+			}
+		}
+	}
+
+	return ret;
+}
+
+static const struct nvkm_event_func
+nouveau_disp_hpd_func = {
+	.ctor = nouveau_disp_hpd_ctor
+};
+
 int
 _nouveau_disp_fini(struct nouveau_object *object, bool suspend)
 {
@@ -97,7 +148,8 @@
 	struct nouveau_disp *disp = (void *)object;
 	struct nvkm_output *outp, *outt;
 
-	nouveau_event_destroy(&disp->vblank);
+	nvkm_event_fini(&disp->vblank);
+	nvkm_event_fini(&disp->hpd);
 
 	if (disp->outp.next) {
 		list_for_each_entry_safe(outp, outt, &disp->outp, head) {
@@ -157,14 +209,11 @@
 		hpd = max(hpd, (u8)(dcbE.connector + 1));
 	}
 
-	ret = nouveau_event_create(3, hpd, &disp->hpd);
+	ret = nvkm_event_init(&nouveau_disp_hpd_func, 3, hpd, &disp->hpd);
 	if (ret)
 		return ret;
 
-	disp->hpd->priv = disp;
-	disp->hpd->check = nouveau_disp_hpd_check;
-
-	ret = nouveau_event_create(1, heads, &disp->vblank);
+	ret = nvkm_event_init(impl->vblank, 1, heads, &disp->vblank);
 	if (ret)
 		return ret;
 
diff --git a/drivers/gpu/drm/nouveau/core/engine/disp/conn.c b/drivers/gpu/drm/nouveau/core/engine/disp/conn.c
index 4ffbc70..3d10702 100644
--- a/drivers/gpu/drm/nouveau/core/engine/disp/conn.c
+++ b/drivers/gpu/drm/nouveau/core/engine/disp/conn.c
@@ -22,39 +22,41 @@
  * Authors: Ben Skeggs
  */
 
+#include <core/os.h>
+#include <nvif/event.h>
+
 #include <subdev/gpio.h>
 
 #include "conn.h"
 #include "outp.h"
 
-static void
-nvkm_connector_hpd_work(struct work_struct *w)
+static int
+nvkm_connector_hpd(struct nvkm_notify *notify)
 {
-	struct nvkm_connector *conn = container_of(w, typeof(*conn), hpd.work);
+	struct nvkm_connector *conn = container_of(notify, typeof(*conn), hpd);
 	struct nouveau_disp *disp = nouveau_disp(conn);
 	struct nouveau_gpio *gpio = nouveau_gpio(conn);
-	u32 send = NVKM_HPD_UNPLUG;
-	if (gpio->get(gpio, 0, DCB_GPIO_UNUSED, conn->hpd.event->index))
-		send = NVKM_HPD_PLUG;
-	nouveau_event_trigger(disp->hpd, send, conn->index);
-	nouveau_event_get(conn->hpd.event);
-}
+	const struct nvkm_gpio_ntfy_rep *line = notify->data;
+	struct nvif_notify_conn_rep_v0 rep;
+	int index = conn->index;
 
-static int
-nvkm_connector_hpd(void *data, u32 type, int index)
-{
-	struct nvkm_connector *conn = data;
-	DBG("HPD: %d\n", type);
-	schedule_work(&conn->hpd.work);
-	return NVKM_EVENT_DROP;
+	DBG("HPD: %d\n", line->mask);
+
+	if (!gpio->get(gpio, 0, DCB_GPIO_UNUSED, conn->hpd.index))
+		rep.mask = NVIF_NOTIFY_CONN_V0_UNPLUG;
+	else
+		rep.mask = NVIF_NOTIFY_CONN_V0_PLUG;
+	rep.version = 0;
+
+	nvkm_event_send(&disp->hpd, rep.mask, index, &rep, sizeof(rep));
+	return NVKM_NOTIFY_KEEP;
 }
 
 int
 _nvkm_connector_fini(struct nouveau_object *object, bool suspend)
 {
 	struct nvkm_connector *conn = (void *)object;
-	if (conn->hpd.event)
-		nouveau_event_put(conn->hpd.event);
+	nvkm_notify_put(&conn->hpd);
 	return nouveau_object_fini(&conn->base, suspend);
 }
 
@@ -63,10 +65,8 @@
 {
 	struct nvkm_connector *conn = (void *)object;
 	int ret = nouveau_object_init(&conn->base);
-	if (ret == 0) {
-		if (conn->hpd.event)
-			nouveau_event_get(conn->hpd.event);
-	}
+	if (ret == 0)
+		nvkm_notify_get(&conn->hpd);
 	return ret;
 }
 
@@ -74,7 +74,7 @@
 _nvkm_connector_dtor(struct nouveau_object *object)
 {
 	struct nvkm_connector *conn = (void *)object;
-	nouveau_event_ref(NULL, &conn->hpd.event);
+	nvkm_notify_fini(&conn->hpd);
 	nouveau_object_destroy(&conn->base);
 }
 
@@ -116,19 +116,24 @@
 	if ((info->hpd = ffs(info->hpd))) {
 		if (--info->hpd >= ARRAY_SIZE(hpd)) {
 			ERR("hpd %02x unknown\n", info->hpd);
-			goto done;
+			return 0;
 		}
 		info->hpd = hpd[info->hpd];
 
 		ret = gpio->find(gpio, 0, info->hpd, DCB_GPIO_UNUSED, &func);
 		if (ret) {
 			ERR("func %02x lookup failed, %d\n", info->hpd, ret);
-			goto done;
+			return 0;
 		}
 
-		ret = nouveau_event_new(gpio->events, NVKM_GPIO_TOGGLED,
-					func.line, nvkm_connector_hpd,
-					conn, &conn->hpd.event);
+		ret = nvkm_notify_init(&gpio->event, nvkm_connector_hpd, true,
+				       &(struct nvkm_gpio_ntfy_req) {
+					.mask = NVKM_GPIO_TOGGLED,
+					.line = func.line,
+				       },
+				       sizeof(struct nvkm_gpio_ntfy_req),
+				       sizeof(struct nvkm_gpio_ntfy_rep),
+				       &conn->hpd);
 		if (ret) {
 			ERR("func %02x failed, %d\n", info->hpd, ret);
 		} else {
@@ -136,8 +141,6 @@
 		}
 	}
 
-done:
-	INIT_WORK(&conn->hpd.work, nvkm_connector_hpd_work);
 	return 0;
 }
 
diff --git a/drivers/gpu/drm/nouveau/core/engine/disp/conn.h b/drivers/gpu/drm/nouveau/core/engine/disp/conn.h
index 035ebea..55e5f5c 100644
--- a/drivers/gpu/drm/nouveau/core/engine/disp/conn.h
+++ b/drivers/gpu/drm/nouveau/core/engine/disp/conn.h
@@ -10,10 +10,7 @@
 	struct nvbios_connE info;
 	int index;
 
-	struct {
-		struct nouveau_eventh *event;
-		struct work_struct work;
-	} hpd;
+	struct nvkm_notify hpd;
 };
 
 #define nvkm_connector_create(p,e,c,b,i,d)                                     \
diff --git a/drivers/gpu/drm/nouveau/core/engine/disp/dport.c b/drivers/gpu/drm/nouveau/core/engine/disp/dport.c
index 5a5b59b..157bda9 100644
--- a/drivers/gpu/drm/nouveau/core/engine/disp/dport.c
+++ b/drivers/gpu/drm/nouveau/core/engine/disp/dport.c
@@ -354,7 +354,7 @@
 	cfg--;
 
 	/* disable link interrupt handling during link training */
-	nouveau_event_put(outp->irq);
+	nvkm_notify_put(&outp->irq);
 
 	/* enable down-spreading and execute pre-train script from vbios */
 	dp_link_train_init(dp, outp->dpcd[3] & 0x01);
@@ -395,5 +395,5 @@
 	DBG("training complete\n");
 	atomic_set(&outp->lt.done, 1);
 	wake_up(&outp->lt.wait);
-	nouveau_event_get(outp->irq);
+	nvkm_notify_get(&outp->irq);
 }
diff --git a/drivers/gpu/drm/nouveau/core/engine/disp/gm107.c b/drivers/gpu/drm/nouveau/core/engine/disp/gm107.c
index 9fc7447..a9681df 100644
--- a/drivers/gpu/drm/nouveau/core/engine/disp/gm107.c
+++ b/drivers/gpu/drm/nouveau/core/engine/disp/gm107.c
@@ -93,6 +93,7 @@
 		.init = _nouveau_disp_init,
 		.fini = _nouveau_disp_fini,
 	},
+	.base.vblank = &nvd0_disp_vblank_func,
 	.base.outp =  nvd0_disp_outp_sclass,
 	.mthd.core = &nve0_disp_mast_mthd_chan,
 	.mthd.base = &nvd0_disp_sync_mthd_chan,
diff --git a/drivers/gpu/drm/nouveau/core/engine/disp/nv04.c b/drivers/gpu/drm/nouveau/core/engine/disp/nv04.c
index a32666e..f1f3bd1 100644
--- a/drivers/gpu/drm/nouveau/core/engine/disp/nv04.c
+++ b/drivers/gpu/drm/nouveau/core/engine/disp/nv04.c
@@ -86,17 +86,26 @@
  ******************************************************************************/
 
 static void
-nv04_disp_vblank_enable(struct nouveau_event *event, int type, int head)
+nv04_disp_vblank_init(struct nvkm_event *event, int type, int head)
 {
-	nv_wr32(event->priv, 0x600140 + (head * 0x2000) , 0x00000001);
+	struct nouveau_disp *disp = container_of(event, typeof(*disp), vblank);
+	nv_wr32(disp, 0x600140 + (head * 0x2000) , 0x00000001);
 }
 
 static void
-nv04_disp_vblank_disable(struct nouveau_event *event, int type, int head)
+nv04_disp_vblank_fini(struct nvkm_event *event, int type, int head)
 {
-	nv_wr32(event->priv, 0x600140 + (head * 0x2000) , 0x00000000);
+	struct nouveau_disp *disp = container_of(event, typeof(*disp), vblank);
+	nv_wr32(disp, 0x600140 + (head * 0x2000) , 0x00000000);
 }
 
+static const struct nvkm_event_func
+nv04_disp_vblank_func = {
+	.ctor = nouveau_disp_vblank_ctor,
+	.init = nv04_disp_vblank_init,
+	.fini = nv04_disp_vblank_fini,
+};
+
 static void
 nv04_disp_intr(struct nouveau_subdev *subdev)
 {
@@ -106,12 +115,12 @@
 	u32 pvideo;
 
 	if (crtc0 & 0x00000001) {
-		nouveau_event_trigger(priv->base.vblank, 1, 0);
+		nouveau_disp_vblank(&priv->base, 0);
 		nv_wr32(priv, 0x600100, 0x00000001);
 	}
 
 	if (crtc1 & 0x00000001) {
-		nouveau_event_trigger(priv->base.vblank, 1, 1);
+		nouveau_disp_vblank(&priv->base, 1);
 		nv_wr32(priv, 0x602100, 0x00000001);
 	}
 
@@ -140,9 +149,6 @@
 
 	nv_engine(priv)->sclass = nv04_disp_sclass;
 	nv_subdev(priv)->intr = nv04_disp_intr;
-	priv->base.vblank->priv = priv;
-	priv->base.vblank->enable = nv04_disp_vblank_enable;
-	priv->base.vblank->disable = nv04_disp_vblank_disable;
 	return 0;
 }
 
@@ -155,4 +161,5 @@
 		.init = _nouveau_disp_init,
 		.fini = _nouveau_disp_fini,
 	},
+	.vblank = &nv04_disp_vblank_func,
 }.base;
diff --git a/drivers/gpu/drm/nouveau/core/engine/disp/nv50.c b/drivers/gpu/drm/nouveau/core/engine/disp/nv50.c
index 2283c44..5311f0f 100644
--- a/drivers/gpu/drm/nouveau/core/engine/disp/nv50.c
+++ b/drivers/gpu/drm/nouveau/core/engine/disp/nv50.c
@@ -828,19 +828,7 @@
 	return 0;
 }
 
-static void
-nv50_disp_base_vblank_enable(struct nouveau_event *event, int type, int head)
-{
-	nv_mask(event->priv, 0x61002c, (4 << head), (4 << head));
-}
-
-static void
-nv50_disp_base_vblank_disable(struct nouveau_event *event, int type, int head)
-{
-	nv_mask(event->priv, 0x61002c, (4 << head), 0);
-}
-
-static int
+int
 nv50_disp_base_ctor(struct nouveau_object *parent,
 		    struct nouveau_object *engine,
 		    struct nouveau_oclass *oclass, void *data, u32 size,
@@ -856,14 +844,11 @@
 	if (ret)
 		return ret;
 
-	priv->base.vblank->priv = priv;
-	priv->base.vblank->enable = nv50_disp_base_vblank_enable;
-	priv->base.vblank->disable = nv50_disp_base_vblank_disable;
 	return nouveau_ramht_new(nv_object(base), nv_object(base), 0x1000, 0,
 				&base->ramht);
 }
 
-static void
+void
 nv50_disp_base_dtor(struct nouveau_object *object)
 {
 	struct nv50_disp_base *base = (void *)object;
@@ -1040,6 +1025,27 @@
  * Display engine implementation
  ******************************************************************************/
 
+static void
+nv50_disp_vblank_fini(struct nvkm_event *event, int type, int head)
+{
+	struct nouveau_disp *disp = container_of(event, typeof(*disp), vblank);
+	nv_mask(disp, 0x61002c, (4 << head), 0);
+}
+
+static void
+nv50_disp_vblank_init(struct nvkm_event *event, int type, int head)
+{
+	struct nouveau_disp *disp = container_of(event, typeof(*disp), vblank);
+	nv_mask(disp, 0x61002c, (4 << head), (4 << head));
+}
+
+const struct nvkm_event_func
+nv50_disp_vblank_func = {
+	.ctor = nouveau_disp_vblank_ctor,
+	.init = nv50_disp_vblank_init,
+	.fini = nv50_disp_vblank_fini,
+};
+
 static const struct nouveau_enum
 nv50_disp_intr_error_type[] = {
 	{ 3, "ILLEGAL_MTHD" },
@@ -1654,13 +1660,13 @@
 	}
 
 	if (intr1 & 0x00000004) {
-		nouveau_event_trigger(priv->base.vblank, 1, 0);
+		nouveau_disp_vblank(&priv->base, 0);
 		nv_wr32(priv, 0x610024, 0x00000004);
 		intr1 &= ~0x00000004;
 	}
 
 	if (intr1 & 0x00000008) {
-		nouveau_event_trigger(priv->base.vblank, 1, 1);
+		nouveau_disp_vblank(&priv->base, 1);
 		nv_wr32(priv, 0x610024, 0x00000008);
 		intr1 &= ~0x00000008;
 	}
@@ -1718,6 +1724,7 @@
 		.init = _nouveau_disp_init,
 		.fini = _nouveau_disp_fini,
 	},
+	.base.vblank = &nv50_disp_vblank_func,
 	.base.outp =  nv50_disp_outp_sclass,
 	.mthd.core = &nv50_disp_mast_mthd_chan,
 	.mthd.base = &nv50_disp_sync_mthd_chan,
diff --git a/drivers/gpu/drm/nouveau/core/engine/disp/nv50.h b/drivers/gpu/drm/nouveau/core/engine/disp/nv50.h
index 1a88647..c0e8b79 100644
--- a/drivers/gpu/drm/nouveau/core/engine/disp/nv50.h
+++ b/drivers/gpu/drm/nouveau/core/engine/disp/nv50.h
@@ -165,11 +165,16 @@
 extern struct nouveau_ofuncs nv50_disp_oimm_ofuncs;
 extern struct nouveau_ofuncs nv50_disp_curs_ofuncs;
 extern struct nouveau_ofuncs nv50_disp_base_ofuncs;
+int  nv50_disp_base_ctor(struct nouveau_object *, struct nouveau_object *,
+			 struct nouveau_oclass *, void *, u32,
+			 struct nouveau_object **);
+void nv50_disp_base_dtor(struct nouveau_object *);
 extern struct nouveau_oclass nv50_disp_cclass;
 void nv50_disp_mthd_chan(struct nv50_disp_priv *, int debug, int head,
 			 const struct nv50_disp_mthd_chan *);
 void nv50_disp_intr_supervisor(struct work_struct *);
 void nv50_disp_intr(struct nouveau_subdev *);
+extern const struct nvkm_event_func nv50_disp_vblank_func;
 
 extern const struct nv50_disp_mthd_chan nv84_disp_mast_mthd_chan;
 extern const struct nv50_disp_mthd_list nv84_disp_mast_mthd_dac;
@@ -195,6 +200,7 @@
 extern struct nouveau_oclass nvd0_disp_cclass;
 void nvd0_disp_intr_supervisor(struct work_struct *);
 void nvd0_disp_intr(struct nouveau_subdev *);
+extern const struct nvkm_event_func nvd0_disp_vblank_func;
 
 extern const struct nv50_disp_mthd_chan nve0_disp_mast_mthd_chan;
 extern const struct nv50_disp_mthd_chan nve0_disp_ovly_mthd_chan;
diff --git a/drivers/gpu/drm/nouveau/core/engine/disp/nv84.c b/drivers/gpu/drm/nouveau/core/engine/disp/nv84.c
index 1cc62e4..dd59774 100644
--- a/drivers/gpu/drm/nouveau/core/engine/disp/nv84.c
+++ b/drivers/gpu/drm/nouveau/core/engine/disp/nv84.c
@@ -276,6 +276,7 @@
 		.init = _nouveau_disp_init,
 		.fini = _nouveau_disp_fini,
 	},
+	.base.vblank = &nv50_disp_vblank_func,
 	.base.outp =  nv50_disp_outp_sclass,
 	.mthd.core = &nv84_disp_mast_mthd_chan,
 	.mthd.base = &nv84_disp_sync_mthd_chan,
diff --git a/drivers/gpu/drm/nouveau/core/engine/disp/nv94.c b/drivers/gpu/drm/nouveau/core/engine/disp/nv94.c
index 4f718a9..03a6b25 100644
--- a/drivers/gpu/drm/nouveau/core/engine/disp/nv94.c
+++ b/drivers/gpu/drm/nouveau/core/engine/disp/nv94.c
@@ -143,6 +143,7 @@
 		.init = _nouveau_disp_init,
 		.fini = _nouveau_disp_fini,
 	},
+	.base.vblank = &nv50_disp_vblank_func,
 	.base.outp =  nv94_disp_outp_sclass,
 	.mthd.core = &nv94_disp_mast_mthd_chan,
 	.mthd.base = &nv84_disp_sync_mthd_chan,
diff --git a/drivers/gpu/drm/nouveau/core/engine/disp/nva0.c b/drivers/gpu/drm/nouveau/core/engine/disp/nva0.c
index 6237a9a..ea1b5a7 100644
--- a/drivers/gpu/drm/nouveau/core/engine/disp/nva0.c
+++ b/drivers/gpu/drm/nouveau/core/engine/disp/nva0.c
@@ -138,6 +138,7 @@
 		.init = _nouveau_disp_init,
 		.fini = _nouveau_disp_fini,
 	},
+	.base.vblank = &nv50_disp_vblank_func,
 	.base.outp =  nv50_disp_outp_sclass,
 	.mthd.core = &nv84_disp_mast_mthd_chan,
 	.mthd.base = &nv84_disp_sync_mthd_chan,
diff --git a/drivers/gpu/drm/nouveau/core/engine/disp/nva3.c b/drivers/gpu/drm/nouveau/core/engine/disp/nva3.c
index 019124d..00f38a3 100644
--- a/drivers/gpu/drm/nouveau/core/engine/disp/nva3.c
+++ b/drivers/gpu/drm/nouveau/core/engine/disp/nva3.c
@@ -110,6 +110,7 @@
 		.init = _nouveau_disp_init,
 		.fini = _nouveau_disp_fini,
 	},
+	.base.vblank = &nv50_disp_vblank_func,
 	.base.outp =  nv94_disp_outp_sclass,
 	.mthd.core = &nv94_disp_mast_mthd_chan,
 	.mthd.base = &nv84_disp_sync_mthd_chan,
diff --git a/drivers/gpu/drm/nouveau/core/engine/disp/nvd0.c b/drivers/gpu/drm/nouveau/core/engine/disp/nvd0.c
index fa30d81..1ab2169 100644
--- a/drivers/gpu/drm/nouveau/core/engine/disp/nvd0.c
+++ b/drivers/gpu/drm/nouveau/core/engine/disp/nvd0.c
@@ -747,50 +747,6 @@
 	return 0;
 }
 
-static void
-nvd0_disp_base_vblank_enable(struct nouveau_event *event, int type, int head)
-{
-	nv_mask(event->priv, 0x6100c0 + (head * 0x800), 0x00000001, 0x00000001);
-}
-
-static void
-nvd0_disp_base_vblank_disable(struct nouveau_event *event, int type, int head)
-{
-	nv_mask(event->priv, 0x6100c0 + (head * 0x800), 0x00000001, 0x00000000);
-}
-
-static int
-nvd0_disp_base_ctor(struct nouveau_object *parent,
-		    struct nouveau_object *engine,
-		    struct nouveau_oclass *oclass, void *data, u32 size,
-		    struct nouveau_object **pobject)
-{
-	struct nv50_disp_priv *priv = (void *)engine;
-	struct nv50_disp_base *base;
-	int ret;
-
-	ret = nouveau_parent_create(parent, engine, oclass, 0,
-				    priv->sclass, 0, &base);
-	*pobject = nv_object(base);
-	if (ret)
-		return ret;
-
-	priv->base.vblank->priv = priv;
-	priv->base.vblank->enable = nvd0_disp_base_vblank_enable;
-	priv->base.vblank->disable = nvd0_disp_base_vblank_disable;
-
-	return nouveau_ramht_new(nv_object(base), nv_object(base), 0x1000, 0,
-				&base->ramht);
-}
-
-static void
-nvd0_disp_base_dtor(struct nouveau_object *object)
-{
-	struct nv50_disp_base *base = (void *)object;
-	nouveau_ramht_ref(NULL, &base->ramht);
-	nouveau_parent_destroy(&base->base);
-}
-
 static int
 nvd0_disp_base_init(struct nouveau_object *object)
 {
@@ -874,8 +830,8 @@
 
 struct nouveau_ofuncs
 nvd0_disp_base_ofuncs = {
-	.ctor = nvd0_disp_base_ctor,
-	.dtor = nvd0_disp_base_dtor,
+	.ctor = nv50_disp_base_ctor,
+	.dtor = nv50_disp_base_dtor,
 	.init = nvd0_disp_base_init,
 	.fini = nvd0_disp_base_fini,
 };
@@ -916,6 +872,27 @@
  * Display engine implementation
  ******************************************************************************/
 
+static void
+nvd0_disp_vblank_init(struct nvkm_event *event, int type, int head)
+{
+	struct nouveau_disp *disp = container_of(event, typeof(*disp), vblank);
+	nv_mask(disp, 0x6100c0 + (head * 0x800), 0x00000001, 0x00000001);
+}
+
+static void
+nvd0_disp_vblank_fini(struct nvkm_event *event, int type, int head)
+{
+	struct nouveau_disp *disp = container_of(event, typeof(*disp), vblank);
+	nv_mask(disp, 0x6100c0 + (head * 0x800), 0x00000001, 0x00000000);
+}
+
+const struct nvkm_event_func
+nvd0_disp_vblank_func = {
+	.ctor = nouveau_disp_vblank_ctor,
+	.init = nvd0_disp_vblank_init,
+	.fini = nvd0_disp_vblank_fini,
+};
+
 static struct nvkm_output *
 exec_lookup(struct nv50_disp_priv *priv, int head, int or, u32 ctrl,
 	    u32 *data, u8 *ver, u8 *hdr, u8 *cnt, u8 *len,
@@ -1343,7 +1320,7 @@
 		if (mask & intr) {
 			u32 stat = nv_rd32(priv, 0x6100bc + (i * 0x800));
 			if (stat & 0x00000001)
-				nouveau_event_trigger(priv->base.vblank, 1, i);
+				nouveau_disp_vblank(&priv->base, i);
 			nv_mask(priv, 0x6100bc + (i * 0x800), 0, 0);
 			nv_rd32(priv, 0x6100c0 + (i * 0x800));
 		}
@@ -1396,6 +1373,7 @@
 		.init = _nouveau_disp_init,
 		.fini = _nouveau_disp_fini,
 	},
+	.base.vblank = &nvd0_disp_vblank_func,
 	.base.outp =  nvd0_disp_outp_sclass,
 	.mthd.core = &nvd0_disp_mast_mthd_chan,
 	.mthd.base = &nvd0_disp_sync_mthd_chan,
diff --git a/drivers/gpu/drm/nouveau/core/engine/disp/nve0.c b/drivers/gpu/drm/nouveau/core/engine/disp/nve0.c
index 11328e3..1e5a79a 100644
--- a/drivers/gpu/drm/nouveau/core/engine/disp/nve0.c
+++ b/drivers/gpu/drm/nouveau/core/engine/disp/nve0.c
@@ -258,6 +258,7 @@
 		.init = _nouveau_disp_init,
 		.fini = _nouveau_disp_fini,
 	},
+	.base.vblank = &nvd0_disp_vblank_func,
 	.base.outp =  nvd0_disp_outp_sclass,
 	.mthd.core = &nve0_disp_mast_mthd_chan,
 	.mthd.base = &nvd0_disp_sync_mthd_chan,
diff --git a/drivers/gpu/drm/nouveau/core/engine/disp/nvf0.c b/drivers/gpu/drm/nouveau/core/engine/disp/nvf0.c
index 1043880..198bc4b 100644
--- a/drivers/gpu/drm/nouveau/core/engine/disp/nvf0.c
+++ b/drivers/gpu/drm/nouveau/core/engine/disp/nvf0.c
@@ -93,6 +93,7 @@
 		.init = _nouveau_disp_init,
 		.fini = _nouveau_disp_fini,
 	},
+	.base.vblank = &nvd0_disp_vblank_func,
 	.base.outp =  nvd0_disp_outp_sclass,
 	.mthd.core = &nve0_disp_mast_mthd_chan,
 	.mthd.base = &nvd0_disp_sync_mthd_chan,
diff --git a/drivers/gpu/drm/nouveau/core/engine/disp/outpdp.c b/drivers/gpu/drm/nouveau/core/engine/disp/outpdp.c
index eb2d778..6f6e2a8 100644
--- a/drivers/gpu/drm/nouveau/core/engine/disp/outpdp.c
+++ b/drivers/gpu/drm/nouveau/core/engine/disp/outpdp.c
@@ -22,6 +22,9 @@
  * Authors: Ben Skeggs
  */
 
+#include <core/os.h>
+#include <nvif/event.h>
+
 #include <subdev/i2c.h>
 
 #include "outpdp.h"
@@ -86,7 +89,7 @@
 		atomic_set(&outp->lt.done, 0);
 		schedule_work(&outp->lt.work);
 	} else {
-		nouveau_event_get(outp->irq);
+		nvkm_notify_get(&outp->irq);
 	}
 
 	if (wait) {
@@ -133,46 +136,59 @@
 	}
 }
 
-static void
-nvkm_output_dp_service_work(struct work_struct *work)
+static int
+nvkm_output_dp_hpd(struct nvkm_notify *notify)
 {
-	struct nvkm_output_dp *outp = container_of(work, typeof(*outp), work);
-	struct nouveau_disp *disp = nouveau_disp(outp);
-	int type = atomic_xchg(&outp->pending, 0);
-	u32 send = 0;
+	struct nvkm_connector *conn = container_of(notify, typeof(*conn), hpd);
+	struct nvkm_output_dp *outp;
+	struct nouveau_disp *disp = nouveau_disp(conn);
+	const struct nvkm_i2c_ntfy_rep *line = notify->data;
+	struct nvif_notify_conn_rep_v0 rep = {};
 
-	if (type & (NVKM_I2C_PLUG | NVKM_I2C_UNPLUG)) {
-		nvkm_output_dp_detect(outp);
-		if (type & NVKM_I2C_UNPLUG)
-			send |= NVKM_HPD_UNPLUG;
-		if (type & NVKM_I2C_PLUG)
-			send |= NVKM_HPD_PLUG;
-		nouveau_event_get(outp->base.conn->hpd.event);
+	list_for_each_entry(outp, &disp->outp, base.head) {
+		if (outp->base.conn == conn &&
+		    outp->info.type == DCB_OUTPUT_DP) {
+			DBG("HPD: %d\n", line->mask);
+			nvkm_output_dp_detect(outp);
+
+			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;
+		}
 	}
 
-	if (type & NVKM_I2C_IRQ) {
-		nvkm_output_dp_train(&outp->base, 0, true);
-		send |= NVKM_HPD_IRQ;
-	}
-
-	nouveau_event_trigger(disp->hpd, send, outp->base.info.connector);
+	WARN_ON(1);
+	return NVKM_NOTIFY_DROP;
 }
 
 static int
-nvkm_output_dp_service(void *data, u32 type, int index)
+nvkm_output_dp_irq(struct nvkm_notify *notify)
 {
-	struct nvkm_output_dp *outp = data;
-	DBG("HPD: %d\n", type);
-	atomic_or(type, &outp->pending);
-	schedule_work(&outp->work);
-	return NVKM_EVENT_DROP;
+	struct nvkm_output_dp *outp = container_of(notify, typeof(*outp), irq);
+	struct nouveau_disp *disp = nouveau_disp(outp);
+	const struct nvkm_i2c_ntfy_rep *line = notify->data;
+	struct nvif_notify_conn_rep_v0 rep = {
+		.mask = NVIF_NOTIFY_CONN_V0_IRQ,
+	};
+	int index = outp->base.info.connector;
+
+	DBG("IRQ: %d\n", line->mask);
+	nvkm_output_dp_train(&outp->base, 0, true);
+
+	nvkm_event_send(&disp->hpd, rep.mask, index, &rep, sizeof(rep));
+	return NVKM_NOTIFY_DROP;
 }
 
 int
 _nvkm_output_dp_fini(struct nouveau_object *object, bool suspend)
 {
 	struct nvkm_output_dp *outp = (void *)object;
-	nouveau_event_put(outp->irq);
+	nvkm_notify_put(&outp->irq);
 	nvkm_output_dp_enable(outp, false);
 	return nvkm_output_fini(&outp->base, suspend);
 }
@@ -189,7 +205,7 @@
 _nvkm_output_dp_dtor(struct nouveau_object *object)
 {
 	struct nvkm_output_dp *outp = (void *)object;
-	nouveau_event_ref(NULL, &outp->irq);
+	nvkm_notify_fini(&outp->irq);
 	nvkm_output_destroy(&outp->base);
 }
 
@@ -213,7 +229,7 @@
 	if (ret)
 		return ret;
 
-	nouveau_event_ref(NULL, &outp->base.conn->hpd.event);
+	nvkm_notify_fini(&outp->base.conn->hpd);
 
 	/* access to the aux channel is not optional... */
 	if (!outp->base.edid) {
@@ -238,20 +254,28 @@
 	atomic_set(&outp->lt.done, 0);
 
 	/* link maintenance */
-	ret = nouveau_event_new(i2c->ntfy, NVKM_I2C_IRQ, outp->base.edid->index,
-				nvkm_output_dp_service, outp, &outp->irq);
+	ret = nvkm_notify_init(&i2c->event, nvkm_output_dp_irq, true,
+			       &(struct nvkm_i2c_ntfy_req) {
+				.mask = NVKM_I2C_IRQ,
+				.port = outp->base.edid->index,
+			       },
+			       sizeof(struct nvkm_i2c_ntfy_req),
+			       sizeof(struct nvkm_i2c_ntfy_rep),
+			       &outp->irq);
 	if (ret) {
 		ERR("error monitoring aux irq event: %d\n", ret);
 		return ret;
 	}
 
-	INIT_WORK(&outp->work, nvkm_output_dp_service_work);
-
 	/* hotplug detect, replaces gpio-based mechanism with aux events */
-	ret = nouveau_event_new(i2c->ntfy, NVKM_I2C_PLUG | NVKM_I2C_UNPLUG,
-				outp->base.edid->index,
-				nvkm_output_dp_service, outp,
-			       &outp->base.conn->hpd.event);
+	ret = nvkm_notify_init(&i2c->event, nvkm_output_dp_hpd, true,
+			       &(struct nvkm_i2c_ntfy_req) {
+				.mask = NVKM_I2C_PLUG | NVKM_I2C_UNPLUG,
+				.port = outp->base.edid->index,
+			       },
+			       sizeof(struct nvkm_i2c_ntfy_req),
+			       sizeof(struct nvkm_i2c_ntfy_rep),
+			       &outp->base.conn->hpd);
 	if (ret) {
 		ERR("error monitoring aux hpd events: %d\n", ret);
 		return ret;
diff --git a/drivers/gpu/drm/nouveau/core/engine/disp/outpdp.h b/drivers/gpu/drm/nouveau/core/engine/disp/outpdp.h
index ff33ba1..1fac367 100644
--- a/drivers/gpu/drm/nouveau/core/engine/disp/outpdp.h
+++ b/drivers/gpu/drm/nouveau/core/engine/disp/outpdp.h
@@ -12,10 +12,7 @@
 	struct nvbios_dpout info;
 	u8 version;
 
-	struct nouveau_eventh *irq;
-	struct nouveau_eventh *hpd;
-	struct work_struct work;
-	atomic_t pending;
+	struct nvkm_notify irq;
 	bool present;
 	u8 dpcd[16];
 
diff --git a/drivers/gpu/drm/nouveau/core/engine/disp/priv.h b/drivers/gpu/drm/nouveau/core/engine/disp/priv.h
index 26e9a42..5506d11 100644
--- a/drivers/gpu/drm/nouveau/core/engine/disp/priv.h
+++ b/drivers/gpu/drm/nouveau/core/engine/disp/priv.h
@@ -11,6 +11,7 @@
 	struct nouveau_oclass base;
 	struct nouveau_oclass **outp;
 	struct nouveau_oclass **conn;
+	const struct nvkm_event_func *vblank;
 };
 
 #define nouveau_disp_create(p,e,c,h,i,x,d)                                     \
@@ -39,4 +40,7 @@
 extern struct nouveau_oclass *nvkm_output_oclass;
 extern struct nouveau_oclass *nvkm_connector_oclass;
 
+int  nouveau_disp_vblank_ctor(void *data, u32 size, struct nvkm_notify *);
+void nouveau_disp_vblank(struct nouveau_disp *, int head);
+
 #endif
diff --git a/drivers/gpu/drm/nouveau/core/engine/disp/sornv50.c b/drivers/gpu/drm/nouveau/core/engine/disp/sornv50.c
index 7a1ebdf..3101248 100644
--- a/drivers/gpu/drm/nouveau/core/engine/disp/sornv50.c
+++ b/drivers/gpu/drm/nouveau/core/engine/disp/sornv50.c
@@ -87,7 +87,7 @@
 			struct nvkm_output_dp *outpdp = (void *)outp;
 			switch (data) {
 			case NV94_DISP_SOR_DP_PWR_STATE_OFF:
-				nouveau_event_put(outpdp->irq);
+				nvkm_notify_put(&outpdp->irq);
 				((struct nvkm_output_dp_impl *)nv_oclass(outp))
 					->lnk_pwr(outpdp, 0);
 				atomic_set(&outpdp->lt.done, 0);
diff --git a/drivers/gpu/drm/nouveau/core/engine/fifo/base.c b/drivers/gpu/drm/nouveau/core/engine/fifo/base.c
index 56ed3d7..812d85d 100644
--- a/drivers/gpu/drm/nouveau/core/engine/fifo/base.c
+++ b/drivers/gpu/drm/nouveau/core/engine/fifo/base.c
@@ -31,6 +31,23 @@
 #include <engine/dmaobj.h>
 #include <engine/fifo.h>
 
+static int
+nouveau_fifo_event_ctor(void *data, u32 size, struct nvkm_notify *notify)
+{
+	if (size == 0) {
+		notify->size  = 0;
+		notify->types = 1;
+		notify->index = 0;
+		return 0;
+	}
+	return -ENOSYS;
+}
+
+static const struct nvkm_event_func
+nouveau_fifo_event_func = {
+	.ctor = nouveau_fifo_event_ctor,
+};
+
 int
 nouveau_fifo_channel_create_(struct nouveau_object *parent,
 			     struct nouveau_object *engine,
@@ -91,7 +108,7 @@
 	if (!chan->user)
 		return -EFAULT;
 
-	nouveau_event_trigger(priv->cevent, 1, 0);
+	nvkm_event_send(&priv->cevent, 1, 0, NULL, 0);
 
 	chan->size = size;
 	return 0;
@@ -168,8 +185,8 @@
 nouveau_fifo_destroy(struct nouveau_fifo *priv)
 {
 	kfree(priv->channel);
-	nouveau_event_destroy(&priv->uevent);
-	nouveau_event_destroy(&priv->cevent);
+	nvkm_event_fini(&priv->uevent);
+	nvkm_event_fini(&priv->cevent);
 	nouveau_engine_destroy(&priv->base);
 }
 
@@ -194,11 +211,7 @@
 	if (!priv->channel)
 		return -ENOMEM;
 
-	ret = nouveau_event_create(1, 1, &priv->cevent);
-	if (ret)
-		return ret;
-
-	ret = nouveau_event_create(1, 1, &priv->uevent);
+	ret = nvkm_event_init(&nouveau_fifo_event_func, 1, 1, &priv->cevent);
 	if (ret)
 		return ret;
 
diff --git a/drivers/gpu/drm/nouveau/core/engine/fifo/nv04.c b/drivers/gpu/drm/nouveau/core/engine/fifo/nv04.c
index c61b16a..921062c 100644
--- a/drivers/gpu/drm/nouveau/core/engine/fifo/nv04.c
+++ b/drivers/gpu/drm/nouveau/core/engine/fifo/nv04.c
@@ -539,7 +539,7 @@
 			}
 
 			if (status & 0x40000000) {
-				nouveau_event_trigger(priv->base.uevent, 1, 0);
+				nvkm_event_send(&priv->base.uevent, 1, 0, NULL, 0);
 				nv_wr32(priv, 0x002100, 0x40000000);
 				status &= ~0x40000000;
 			}
diff --git a/drivers/gpu/drm/nouveau/core/engine/fifo/nv84.c b/drivers/gpu/drm/nouveau/core/engine/fifo/nv84.c
index 6e5ac16..0bf844d 100644
--- a/drivers/gpu/drm/nouveau/core/engine/fifo/nv84.c
+++ b/drivers/gpu/drm/nouveau/core/engine/fifo/nv84.c
@@ -389,20 +389,39 @@
  ******************************************************************************/
 
 static void
-nv84_fifo_uevent_enable(struct nouveau_event *event, int type, int index)
+nv84_fifo_uevent_init(struct nvkm_event *event, int type, int index)
 {
-	struct nv84_fifo_priv *priv = event->priv;
-	nv_mask(priv, 0x002140, 0x40000000, 0x40000000);
+	struct nouveau_fifo *fifo = container_of(event, typeof(*fifo), uevent);
+	nv_mask(fifo, 0x002140, 0x40000000, 0x40000000);
 }
 
 static void
-nv84_fifo_uevent_disable(struct nouveau_event *event, int type, int index)
+nv84_fifo_uevent_fini(struct nvkm_event *event, int type, int index)
 {
-	struct nv84_fifo_priv *priv = event->priv;
-	nv_mask(priv, 0x002140, 0x40000000, 0x00000000);
+	struct nouveau_fifo *fifo = container_of(event, typeof(*fifo), uevent);
+	nv_mask(fifo, 0x002140, 0x40000000, 0x00000000);
 }
 
 static int
+nv84_fifo_uevent_ctor(void *data, u32 size, struct nvkm_notify *notify)
+{
+	if (size == 0) {
+		notify->size  = 0;
+		notify->types = 1;
+		notify->index = 0;
+		return 0;
+	}
+	return -ENOSYS;
+}
+
+static const struct nvkm_event_func
+nv84_fifo_uevent_func = {
+	.ctor = nv84_fifo_uevent_ctor,
+	.init = nv84_fifo_uevent_init,
+	.fini = nv84_fifo_uevent_fini,
+};
+
+static int
 nv84_fifo_ctor(struct nouveau_object *parent, struct nouveau_object *engine,
 	       struct nouveau_oclass *oclass, void *data, u32 size,
 	       struct nouveau_object **pobject)
@@ -425,9 +444,9 @@
 	if (ret)
 		return ret;
 
-	priv->base.uevent->enable = nv84_fifo_uevent_enable;
-	priv->base.uevent->disable = nv84_fifo_uevent_disable;
-	priv->base.uevent->priv = priv;
+	ret = nvkm_event_init(&nv84_fifo_uevent_func, 1, 1, &priv->base.uevent);
+	if (ret)
+		return ret;
 
 	nv_subdev(priv)->unit = 0x00000100;
 	nv_subdev(priv)->intr = nv04_fifo_intr;
diff --git a/drivers/gpu/drm/nouveau/core/engine/fifo/nvc0.c b/drivers/gpu/drm/nouveau/core/engine/fifo/nvc0.c
index ae4a4dc..bd7bf3c 100644
--- a/drivers/gpu/drm/nouveau/core/engine/fifo/nvc0.c
+++ b/drivers/gpu/drm/nouveau/core/engine/fifo/nvc0.c
@@ -730,7 +730,7 @@
 	for (unkn = 0; unkn < 8; unkn++) {
 		u32 ints = (intr >> (unkn * 0x04)) & inte;
 		if (ints & 0x1) {
-			nouveau_event_trigger(priv->base.uevent, 1, 0);
+			nvkm_event_send(&priv->base.uevent, 1, 0, NULL, 0);
 			ints &= ~1;
 		}
 		if (ints) {
@@ -827,20 +827,39 @@
 }
 
 static void
-nvc0_fifo_uevent_enable(struct nouveau_event *event, int type, int index)
+nvc0_fifo_uevent_init(struct nvkm_event *event, int type, int index)
 {
-	struct nvc0_fifo_priv *priv = event->priv;
-	nv_mask(priv, 0x002140, 0x80000000, 0x80000000);
+	struct nouveau_fifo *fifo = container_of(event, typeof(*fifo), uevent);
+	nv_mask(fifo, 0x002140, 0x80000000, 0x80000000);
 }
 
 static void
-nvc0_fifo_uevent_disable(struct nouveau_event *event, int type, int index)
+nvc0_fifo_uevent_fini(struct nvkm_event *event, int type, int index)
 {
-	struct nvc0_fifo_priv *priv = event->priv;
-	nv_mask(priv, 0x002140, 0x80000000, 0x00000000);
+	struct nouveau_fifo *fifo = container_of(event, typeof(*fifo), uevent);
+	nv_mask(fifo, 0x002140, 0x80000000, 0x00000000);
 }
 
 static int
+nvc0_fifo_uevent_ctor(void *data, u32 size, struct nvkm_notify *notify)
+{
+	if (size == 0) {
+		notify->size  = 0;
+		notify->types = 1;
+		notify->index = 0;
+		return 0;
+	}
+	return -ENOSYS;
+}
+
+static const struct nvkm_event_func
+nvc0_fifo_uevent_func = {
+	.ctor = nvc0_fifo_uevent_ctor,
+	.init = nvc0_fifo_uevent_init,
+	.fini = nvc0_fifo_uevent_fini,
+};
+
+static int
 nvc0_fifo_ctor(struct nouveau_object *parent, struct nouveau_object *engine,
 	       struct nouveau_oclass *oclass, void *data, u32 size,
 	       struct nouveau_object **pobject)
@@ -877,9 +896,9 @@
 	if (ret)
 		return ret;
 
-	priv->base.uevent->enable = nvc0_fifo_uevent_enable;
-	priv->base.uevent->disable = nvc0_fifo_uevent_disable;
-	priv->base.uevent->priv = priv;
+	ret = nvkm_event_init(&nvc0_fifo_uevent_func, 1, 1, &priv->base.uevent);
+	if (ret)
+		return ret;
 
 	nv_subdev(priv)->unit = 0x00000100;
 	nv_subdev(priv)->intr = nvc0_fifo_intr;
diff --git a/drivers/gpu/drm/nouveau/core/engine/fifo/nve0.c b/drivers/gpu/drm/nouveau/core/engine/fifo/nve0.c
index 298063e..3e7f03d 100644
--- a/drivers/gpu/drm/nouveau/core/engine/fifo/nve0.c
+++ b/drivers/gpu/drm/nouveau/core/engine/fifo/nve0.c
@@ -859,7 +859,7 @@
 static void
 nve0_fifo_intr_engine(struct nve0_fifo_priv *priv)
 {
-	nouveau_event_trigger(priv->base.uevent, 1, 0);
+	nvkm_event_send(&priv->base.uevent, 1, 0, NULL, 0);
 }
 
 static void
@@ -952,19 +952,38 @@
 }
 
 static void
-nve0_fifo_uevent_enable(struct nouveau_event *event, int type, int index)
+nve0_fifo_uevent_init(struct nvkm_event *event, int type, int index)
 {
-	struct nve0_fifo_priv *priv = event->priv;
-	nv_mask(priv, 0x002140, 0x80000000, 0x80000000);
+	struct nouveau_fifo *fifo = container_of(event, typeof(*fifo), uevent);
+	nv_mask(fifo, 0x002140, 0x80000000, 0x80000000);
 }
 
 static void
-nve0_fifo_uevent_disable(struct nouveau_event *event, int type, int index)
+nve0_fifo_uevent_fini(struct nvkm_event *event, int type, int index)
 {
-	struct nve0_fifo_priv *priv = event->priv;
-	nv_mask(priv, 0x002140, 0x80000000, 0x00000000);
+	struct nouveau_fifo *fifo = container_of(event, typeof(*fifo), uevent);
+	nv_mask(fifo, 0x002140, 0x80000000, 0x00000000);
 }
 
+static int
+nve0_fifo_uevent_ctor(void *data, u32 size, struct nvkm_notify *notify)
+{
+	if (size == 0) {
+		notify->size  = 0;
+		notify->types = 1;
+		notify->index = 0;
+		return 0;
+	}
+	return -ENOSYS;
+}
+
+static const struct nvkm_event_func
+nve0_fifo_uevent_func = {
+	.ctor = nve0_fifo_uevent_ctor,
+	.init = nve0_fifo_uevent_init,
+	.fini = nve0_fifo_uevent_fini,
+};
+
 int
 nve0_fifo_fini(struct nouveau_object *object, bool suspend)
 {
@@ -1067,9 +1086,9 @@
 	if (ret)
 		return ret;
 
-	priv->base.uevent->enable = nve0_fifo_uevent_enable;
-	priv->base.uevent->disable = nve0_fifo_uevent_disable;
-	priv->base.uevent->priv = priv;
+	ret = nvkm_event_init(&nve0_fifo_uevent_func, 1, 1, &priv->base.uevent);
+	if (ret)
+		return ret;
 
 	nv_subdev(priv)->unit = 0x00000100;
 	nv_subdev(priv)->intr = nve0_fifo_intr;
diff --git a/drivers/gpu/drm/nouveau/core/engine/software/nv50.c b/drivers/gpu/drm/nouveau/core/engine/software/nv50.c
index c150fff..48678ed 100644
--- a/drivers/gpu/drm/nouveau/core/engine/software/nv50.c
+++ b/drivers/gpu/drm/nouveau/core/engine/software/nv50.c
@@ -29,6 +29,7 @@
 #include <core/handle.h>
 #include <core/gpuobj.h>
 #include <core/event.h>
+#include <nvif/event.h>
 
 #include <subdev/bar.h>
 
@@ -86,10 +87,10 @@
 {
 	struct nv50_software_chan *chan = (void *)nv_engctx(object->parent);
 	u32 head = *(u32 *)args;
-	if (head >= chan->vblank.nr_event)
+	if (head >= nouveau_disp(chan)->vblank.index_nr)
 		return -EINVAL;
 
-	nouveau_event_get(chan->vblank.event[head]);
+	nvkm_notify_get(&chan->vblank.notify[head]);
 	return 0;
 }
 
@@ -124,9 +125,10 @@
  ******************************************************************************/
 
 static int
-nv50_software_vblsem_release(void *data, u32 type, int head)
+nv50_software_vblsem_release(struct nvkm_notify *notify)
 {
-	struct nv50_software_chan *chan = data;
+	struct nv50_software_chan *chan =
+		container_of(notify, typeof(*chan), vblank.notify[notify->index]);
 	struct nv50_software_priv *priv = (void *)nv_object(chan)->engine;
 	struct nouveau_bar *bar = nouveau_bar(priv);
 
@@ -142,7 +144,7 @@
 		nv_wr32(priv, 0x060014, chan->vblank.value);
 	}
 
-	return NVKM_EVENT_DROP;
+	return NVKM_NOTIFY_DROP;
 }
 
 void
@@ -151,11 +153,8 @@
 	struct nv50_software_chan *chan = (void *)object;
 	int i;
 
-	if (chan->vblank.event) {
-		for (i = 0; i < chan->vblank.nr_event; i++)
-			nouveau_event_ref(NULL, &chan->vblank.event[i]);
-		kfree(chan->vblank.event);
-	}
+	for (i = 0; i < ARRAY_SIZE(chan->vblank.notify); i++)
+		nvkm_notify_fini(&chan->vblank.notify[i]);
 
 	nouveau_software_context_destroy(&chan->base);
 }
@@ -176,15 +175,14 @@
 	if (ret)
 		return ret;
 
-	chan->vblank.nr_event = pdisp ? pdisp->vblank->index_nr : 0;
-	chan->vblank.event = kzalloc(chan->vblank.nr_event *
-				     sizeof(*chan->vblank.event), GFP_KERNEL);
-	if (!chan->vblank.event)
-		return -ENOMEM;
-
-	for (i = 0; i < chan->vblank.nr_event; i++) {
-		ret = nouveau_event_new(pdisp->vblank, 1, i, pclass->vblank,
-					chan, &chan->vblank.event[i]);
+	for (i = 0; pdisp && i < pdisp->vblank.index_nr; i++) {
+		ret = nvkm_notify_init(&pdisp->vblank, pclass->vblank, false,
+				       &(struct nvif_notify_head_req_v0) {
+					.head = i,
+				       },
+				       sizeof(struct nvif_notify_head_req_v0),
+				       sizeof(struct nvif_notify_head_rep_v0),
+				       &chan->vblank.notify[i]);
 		if (ret)
 			return ret;
 	}
diff --git a/drivers/gpu/drm/nouveau/core/engine/software/nv50.h b/drivers/gpu/drm/nouveau/core/engine/software/nv50.h
index bb49a7a..41542e7 100644
--- a/drivers/gpu/drm/nouveau/core/engine/software/nv50.h
+++ b/drivers/gpu/drm/nouveau/core/engine/software/nv50.h
@@ -19,14 +19,13 @@
 
 struct nv50_software_cclass {
 	struct nouveau_oclass base;
-	int (*vblank)(void *, u32, int);
+	int (*vblank)(struct nvkm_notify *);
 };
 
 struct nv50_software_chan {
 	struct nouveau_software_chan base;
 	struct {
-		struct nouveau_eventh **event;
-		int nr_event;
+		struct nvkm_notify notify[4];
 		u32 channel;
 		u32 ctxdma;
 		u64 offset;
diff --git a/drivers/gpu/drm/nouveau/core/engine/software/nvc0.c b/drivers/gpu/drm/nouveau/core/engine/software/nvc0.c
index 6be97b0..df299a9 100644
--- a/drivers/gpu/drm/nouveau/core/engine/software/nvc0.c
+++ b/drivers/gpu/drm/nouveau/core/engine/software/nvc0.c
@@ -104,9 +104,10 @@
  ******************************************************************************/
 
 static int
-nvc0_software_vblsem_release(void *data, u32 type, int head)
+nvc0_software_vblsem_release(struct nvkm_notify *notify)
 {
-	struct nv50_software_chan *chan = data;
+	struct nv50_software_chan *chan =
+		container_of(notify, typeof(*chan), vblank.notify[notify->index]);
 	struct nv50_software_priv *priv = (void *)nv_object(chan)->engine;
 	struct nouveau_bar *bar = nouveau_bar(priv);
 
@@ -116,7 +117,7 @@
 	nv_wr32(priv, 0x060010, lower_32_bits(chan->vblank.offset));
 	nv_wr32(priv, 0x060014, chan->vblank.value);
 
-	return NVKM_EVENT_DROP;
+	return NVKM_NOTIFY_DROP;
 }
 
 static struct nv50_software_cclass
diff --git a/drivers/gpu/drm/nouveau/core/include/core/device.h b/drivers/gpu/drm/nouveau/core/include/core/device.h
index 03c039d..0e41036 100644
--- a/drivers/gpu/drm/nouveau/core/include/core/device.h
+++ b/drivers/gpu/drm/nouveau/core/include/core/device.h
@@ -62,11 +62,6 @@
 	NVDEV_SUBDEV_NR,
 };
 
-enum nvkm_device_ntfy {
-	NVKM_DEVICE_NTFY_POWER = 0,
-	NVKM_DEVICE_NTFY
-};
-
 struct nouveau_device {
 	struct nouveau_engine base;
 	struct list_head head;
@@ -75,7 +70,7 @@
 	struct platform_device *platformdev;
 	u64 handle;
 
-	struct nouveau_event *ntfy;
+	struct nvkm_event event;
 
 	const char *cfgopt;
 	const char *dbgopt;
diff --git a/drivers/gpu/drm/nouveau/core/include/core/event.h b/drivers/gpu/drm/nouveau/core/include/core/event.h
index ba3f1a7..51e55d0 100644
--- a/drivers/gpu/drm/nouveau/core/include/core/event.h
+++ b/drivers/gpu/drm/nouveau/core/include/core/event.h
@@ -1,47 +1,34 @@
 #ifndef __NVKM_EVENT_H__
 #define __NVKM_EVENT_H__
 
-/* return codes from event handlers */
-#define NVKM_EVENT_DROP 0
-#define NVKM_EVENT_KEEP 1
+#include <core/notify.h>
 
-/* nouveau_eventh.flags bit #s */
-#define NVKM_EVENT_ENABLE 0
-
-struct nouveau_eventh {
-	struct nouveau_event *event;
-	struct list_head head;
-	unsigned long flags;
-	u32 types;
-	int index;
-	int (*func)(void *, u32, int);
-	void *priv;
+struct nvkm_event_func {
+	int  (*ctor)(void *data, u32 size, struct nvkm_notify *);
+	void (*send)(void *data, u32 size, struct nvkm_notify *);
+	void (*init)(struct nvkm_event *, int type, int index);
+	void (*fini)(struct nvkm_event *, int type, int index);
 };
 
-struct nouveau_event {
-	void *priv;
-	int (*check)(struct nouveau_event *, u32 type, int index);
-	void (*enable)(struct nouveau_event *, int type, int index);
-	void (*disable)(struct nouveau_event *, int type, int index);
+struct nvkm_event {
+	const struct nvkm_event_func *func;
 
 	int types_nr;
 	int index_nr;
 
-	spinlock_t list_lock;
-	struct list_head *list;
 	spinlock_t refs_lock;
-	int refs[];
+	spinlock_t list_lock;
+	struct list_head list;
+	int *refs;
 };
 
-int  nouveau_event_create(int types_nr, int index_nr, struct nouveau_event **);
-void nouveau_event_destroy(struct nouveau_event **);
-void nouveau_event_trigger(struct nouveau_event *, u32 types, int index);
-
-int  nouveau_event_new(struct nouveau_event *, u32 types, int index,
-		       int (*func)(void *, u32, int), void *,
-		       struct nouveau_eventh **);
-void nouveau_event_ref(struct nouveau_eventh *, struct nouveau_eventh **);
-void nouveau_event_get(struct nouveau_eventh *);
-void nouveau_event_put(struct nouveau_eventh *);
+int  nvkm_event_init(const struct nvkm_event_func *func,
+		     int types_nr, int index_nr,
+		     struct nvkm_event *);
+void nvkm_event_fini(struct nvkm_event *);
+void nvkm_event_get(struct nvkm_event *, u32 types, int index);
+void nvkm_event_put(struct nvkm_event *, u32 types, int index);
+void nvkm_event_send(struct nvkm_event *, u32 types, int index,
+		     void *data, u32 size);
 
 #endif
diff --git a/drivers/gpu/drm/nouveau/core/include/core/notify.h b/drivers/gpu/drm/nouveau/core/include/core/notify.h
new file mode 100644
index 0000000..5df2cba
--- /dev/null
+++ b/drivers/gpu/drm/nouveau/core/include/core/notify.h
@@ -0,0 +1,36 @@
+#ifndef __NVKM_NOTIFY_H__
+#define __NVKM_NOTIFY_H__
+
+struct nvkm_notify {
+	struct nvkm_event *event;
+	struct list_head head;
+#define NVKM_NOTIFY_USER 0
+#define NVKM_NOTIFY_WORK 1
+	unsigned long flags;
+	int block;
+#define NVKM_NOTIFY_DROP 0
+#define NVKM_NOTIFY_KEEP 1
+	int (*func)(struct nvkm_notify *);
+
+	/* set by nvkm_event ctor */
+	u32 types;
+	int index;
+	u8  size;
+
+	struct work_struct work;
+	/* this is const for a *very* good reason - the data might be on the
+	 * stack from an irq handler.  if you're not core/notify.c then you
+	 * should probably think twice before casting it away...
+	 */
+	const void *data;
+};
+
+int  nvkm_notify_init(struct nvkm_event *, int (*func)(struct nvkm_notify *),
+		      bool work, void *data, u32 size, u32 reply,
+		      struct nvkm_notify *);
+void nvkm_notify_fini(struct nvkm_notify *);
+void nvkm_notify_get(struct nvkm_notify *);
+void nvkm_notify_put(struct nvkm_notify *);
+void nvkm_notify_send(struct nvkm_notify *, void *data, u32 size);
+
+#endif
diff --git a/drivers/gpu/drm/nouveau/core/include/engine/disp.h b/drivers/gpu/drm/nouveau/core/include/engine/disp.h
index fde8428..7a64f34 100644
--- a/drivers/gpu/drm/nouveau/core/include/engine/disp.h
+++ b/drivers/gpu/drm/nouveau/core/include/engine/disp.h
@@ -6,20 +6,13 @@
 #include <core/device.h>
 #include <core/event.h>
 
-enum nvkm_hpd_event {
-	NVKM_HPD_PLUG = 1,
-	NVKM_HPD_UNPLUG = 2,
-	NVKM_HPD_IRQ = 4,
-	NVKM_HPD = (NVKM_HPD_PLUG | NVKM_HPD_UNPLUG | NVKM_HPD_IRQ)
-};
-
 struct nouveau_disp {
 	struct nouveau_engine base;
 
 	struct list_head outp;
-	struct nouveau_event *hpd;
 
-	struct nouveau_event *vblank;
+	struct nvkm_event hpd;
+	struct nvkm_event vblank;
 };
 
 static inline struct nouveau_disp *
diff --git a/drivers/gpu/drm/nouveau/core/include/engine/fifo.h b/drivers/gpu/drm/nouveau/core/include/engine/fifo.h
index b639eb2c7..8356712 100644
--- a/drivers/gpu/drm/nouveau/core/include/engine/fifo.h
+++ b/drivers/gpu/drm/nouveau/core/include/engine/fifo.h
@@ -65,8 +65,8 @@
 struct nouveau_fifo {
 	struct nouveau_engine base;
 
-	struct nouveau_event *cevent; /* channel creation event */
-	struct nouveau_event *uevent; /* async user trigger */
+	struct nvkm_event cevent; /* channel creation event */
+	struct nvkm_event uevent; /* async user trigger */
 
 	struct nouveau_object **channel;
 	spinlock_t lock;
diff --git a/drivers/gpu/drm/nouveau/core/include/nvif/event.h b/drivers/gpu/drm/nouveau/core/include/nvif/event.h
new file mode 120000
index 0000000..1b79853
--- /dev/null
+++ b/drivers/gpu/drm/nouveau/core/include/nvif/event.h
@@ -0,0 +1 @@
+../../../nvif/event.h
\ No newline at end of file
diff --git a/drivers/gpu/drm/nouveau/core/include/nvif/unpack.h b/drivers/gpu/drm/nouveau/core/include/nvif/unpack.h
new file mode 120000
index 0000000..69d9929
--- /dev/null
+++ b/drivers/gpu/drm/nouveau/core/include/nvif/unpack.h
@@ -0,0 +1 @@
+../../../nvif/unpack.h
\ No newline at end of file
diff --git a/drivers/gpu/drm/nouveau/core/include/subdev/clock.h b/drivers/gpu/drm/nouveau/core/include/subdev/clock.h
index 0d30fa0..a5ca00d 100644
--- a/drivers/gpu/drm/nouveau/core/include/subdev/clock.h
+++ b/drivers/gpu/drm/nouveau/core/include/subdev/clock.h
@@ -75,7 +75,7 @@
 	wait_queue_head_t wait;
 	atomic_t waiting;
 
-	struct nouveau_eventh *pwrsrc_ntfy;
+	struct nvkm_notify pwrsrc_ntfy;
 	int pwrsrc;
 	int pstate; /* current */
 	int ustate_ac; /* user-requested (-1 disabled, -2 perfmon) */
diff --git a/drivers/gpu/drm/nouveau/core/include/subdev/gpio.h b/drivers/gpu/drm/nouveau/core/include/subdev/gpio.h
index 612d82a..b73733d 100644
--- a/drivers/gpu/drm/nouveau/core/include/subdev/gpio.h
+++ b/drivers/gpu/drm/nouveau/core/include/subdev/gpio.h
@@ -8,16 +8,22 @@
 #include <subdev/bios.h>
 #include <subdev/bios/gpio.h>
 
-enum nvkm_gpio_event {
-	NVKM_GPIO_HI = 1,
-	NVKM_GPIO_LO = 2,
-	NVKM_GPIO_TOGGLED = (NVKM_GPIO_HI | NVKM_GPIO_LO),
+struct nvkm_gpio_ntfy_req {
+#define NVKM_GPIO_HI                                                       0x01
+#define NVKM_GPIO_LO                                                       0x02
+#define NVKM_GPIO_TOGGLED                                                  0x03
+	u8 mask;
+	u8 line;
+};
+
+struct nvkm_gpio_ntfy_rep {
+	u8 mask;
 };
 
 struct nouveau_gpio {
 	struct nouveau_subdev base;
 
-	struct nouveau_event *events;
+	struct nvkm_event event;
 
 	void (*reset)(struct nouveau_gpio *, u8 func);
 	int  (*find)(struct nouveau_gpio *, int idx, u8 tag, u8 line,
diff --git a/drivers/gpu/drm/nouveau/core/include/subdev/i2c.h b/drivers/gpu/drm/nouveau/core/include/subdev/i2c.h
index 825f7bb..1b937c2c 100644
--- a/drivers/gpu/drm/nouveau/core/include/subdev/i2c.h
+++ b/drivers/gpu/drm/nouveau/core/include/subdev/i2c.h
@@ -14,15 +14,18 @@
 #define NV_I2C_TYPE_EXTDDC(e) (0x0005 | (e) << 8)
 #define NV_I2C_TYPE_EXTAUX(e) (0x0006 | (e) << 8)
 
-enum nvkm_i2c_event {
-	NVKM_I2C_PLUG = 1,
-	NVKM_I2C_UNPLUG = 2,
-	NVKM_I2C_IRQ = 4,
-	NVKM_I2C_DONE = 8,
-	NVKM_I2C_ANY = (NVKM_I2C_PLUG |
-			NVKM_I2C_UNPLUG |
-			NVKM_I2C_IRQ |
-			NVKM_I2C_DONE),
+struct nvkm_i2c_ntfy_req {
+#define NVKM_I2C_PLUG                                                      0x01
+#define NVKM_I2C_UNPLUG                                                    0x02
+#define NVKM_I2C_IRQ                                                       0x04
+#define NVKM_I2C_DONE                                                      0x08
+#define NVKM_I2C_ANY                                                       0x0f
+	u8 mask;
+	u8 port;
+};
+
+struct nvkm_i2c_ntfy_rep {
+	u8 mask;
 };
 
 struct nouveau_i2c_port {
@@ -56,7 +59,7 @@
 
 struct nouveau_i2c {
 	struct nouveau_subdev base;
-	struct nouveau_event *ntfy;
+	struct nvkm_event event;
 
 	struct nouveau_i2c_port *(*find)(struct nouveau_i2c *, u8 index);
 	struct nouveau_i2c_port *(*find_type)(struct nouveau_i2c *, u16 type);
diff --git a/drivers/gpu/drm/nouveau/core/subdev/clock/base.c b/drivers/gpu/drm/nouveau/core/subdev/clock/base.c
index d3f70d8..a276a71 100644
--- a/drivers/gpu/drm/nouveau/core/subdev/clock/base.c
+++ b/drivers/gpu/drm/nouveau/core/subdev/clock/base.c
@@ -235,7 +235,7 @@
 	}
 
 	wake_up_all(&clk->wait);
-	nouveau_event_get(clk->pwrsrc_ntfy);
+	nvkm_notify_get(&clk->pwrsrc_ntfy);
 }
 
 static int
@@ -460,11 +460,12 @@
 }
 
 static int
-nouveau_clock_pwrsrc(void *data, u32 mask, int type)
+nouveau_clock_pwrsrc(struct nvkm_notify *notify)
 {
-	struct nouveau_clock *clk = data;
+	struct nouveau_clock *clk =
+		container_of(notify, typeof(*clk), pwrsrc_ntfy);
 	nouveau_pstate_calc(clk, false);
-	return NVKM_EVENT_DROP;
+	return NVKM_NOTIFY_DROP;
 }
 
 /******************************************************************************
@@ -475,7 +476,7 @@
 _nouveau_clock_fini(struct nouveau_object *object, bool suspend)
 {
 	struct nouveau_clock *clk = (void *)object;
-	nouveau_event_put(clk->pwrsrc_ntfy);
+	nvkm_notify_put(&clk->pwrsrc_ntfy);
 	return nouveau_subdev_fini(&clk->base, suspend);
 }
 
@@ -520,7 +521,7 @@
 	struct nouveau_clock *clk = (void *)object;
 	struct nouveau_pstate *pstate, *temp;
 
-	nouveau_event_ref(NULL, &clk->pwrsrc_ntfy);
+	nvkm_notify_fini(&clk->pwrsrc_ntfy);
 
 	list_for_each_entry_safe(pstate, temp, &clk->states, head) {
 		nouveau_pstate_del(pstate);
@@ -572,9 +573,8 @@
 
 	clk->allow_reclock = allow_reclock;
 
-	ret = nouveau_event_new(device->ntfy, 1, NVKM_DEVICE_NTFY_POWER,
-				nouveau_clock_pwrsrc, clk,
-			       &clk->pwrsrc_ntfy);
+	ret = nvkm_notify_init(&device->event, nouveau_clock_pwrsrc, true,
+			       NULL, 0, 0, &clk->pwrsrc_ntfy);
 	if (ret)
 		return ret;
 
diff --git a/drivers/gpu/drm/nouveau/core/subdev/gpio/base.c b/drivers/gpu/drm/nouveau/core/subdev/gpio/base.c
index 45e0202..b1e3ed7 100644
--- a/drivers/gpu/drm/nouveau/core/subdev/gpio/base.c
+++ b/drivers/gpu/drm/nouveau/core/subdev/gpio/base.c
@@ -106,39 +106,59 @@
 }
 
 static void
-nouveau_gpio_intr_disable(struct nouveau_event *event, int type, int index)
+nouveau_gpio_intr_fini(struct nvkm_event *event, int type, int index)
 {
-	struct nouveau_gpio *gpio = nouveau_gpio(event->priv);
+	struct nouveau_gpio *gpio = container_of(event, typeof(*gpio), event);
 	const struct nouveau_gpio_impl *impl = (void *)nv_object(gpio)->oclass;
 	impl->intr_mask(gpio, type, 1 << index, 0);
 }
 
 static void
-nouveau_gpio_intr_enable(struct nouveau_event *event, int type, int index)
+nouveau_gpio_intr_init(struct nvkm_event *event, int type, int index)
 {
-	struct nouveau_gpio *gpio = nouveau_gpio(event->priv);
+	struct nouveau_gpio *gpio = container_of(event, typeof(*gpio), event);
 	const struct nouveau_gpio_impl *impl = (void *)nv_object(gpio)->oclass;
 	impl->intr_mask(gpio, type, 1 << index, 1 << index);
 }
 
+static int
+nouveau_gpio_intr_ctor(void *data, u32 size, struct nvkm_notify *notify)
+{
+	struct nvkm_gpio_ntfy_req *req = data;
+	if (!WARN_ON(size != sizeof(*req))) {
+		notify->size  = sizeof(struct nvkm_gpio_ntfy_rep);
+		notify->types = req->mask;
+		notify->index = req->line;
+		return 0;
+	}
+	return -EINVAL;
+}
+
 static void
 nouveau_gpio_intr(struct nouveau_subdev *subdev)
 {
 	struct nouveau_gpio *gpio = nouveau_gpio(subdev);
 	const struct nouveau_gpio_impl *impl = (void *)nv_object(gpio)->oclass;
-	u32 hi, lo, e, i;
+	u32 hi, lo, i;
 
 	impl->intr_stat(gpio, &hi, &lo);
 
-	for (i = 0; e = 0, (hi | lo) && i < impl->lines; i++) {
-		if (hi & (1 << i))
-			e |= NVKM_GPIO_HI;
-		if (lo & (1 << i))
-			e |= NVKM_GPIO_LO;
-		nouveau_event_trigger(gpio->events, e, i);
+	for (i = 0; (hi | lo) && i < impl->lines; i++) {
+		struct nvkm_gpio_ntfy_rep rep = {
+			.mask = (NVKM_GPIO_HI * !!(hi & (1 << i))) |
+				(NVKM_GPIO_LO * !!(lo & (1 << i))),
+		};
+		nvkm_event_send(&gpio->event, rep.mask, i, &rep, sizeof(rep));
 	}
 }
 
+static const struct nvkm_event_func
+nouveau_gpio_intr_func = {
+	.ctor = nouveau_gpio_intr_ctor,
+	.init = nouveau_gpio_intr_init,
+	.fini = nouveau_gpio_intr_fini,
+};
+
 int
 _nouveau_gpio_fini(struct nouveau_object *object, bool suspend)
 {
@@ -183,7 +203,7 @@
 _nouveau_gpio_dtor(struct nouveau_object *object)
 {
 	struct nouveau_gpio *gpio = (void *)object;
-	nouveau_event_destroy(&gpio->events);
+	nvkm_event_fini(&gpio->event);
 	nouveau_subdev_destroy(&gpio->base);
 }
 
@@ -208,13 +228,11 @@
 	gpio->get  = nouveau_gpio_get;
 	gpio->reset = impl->reset;
 
-	ret = nouveau_event_create(2, impl->lines, &gpio->events);
+	ret = nvkm_event_init(&nouveau_gpio_intr_func, 2, impl->lines,
+			      &gpio->event);
 	if (ret)
 		return ret;
 
-	gpio->events->priv = gpio;
-	gpio->events->enable = nouveau_gpio_intr_enable;
-	gpio->events->disable = nouveau_gpio_intr_disable;
 	nv_subdev(gpio)->intr = nouveau_gpio_intr;
 	return 0;
 }
diff --git a/drivers/gpu/drm/nouveau/core/subdev/i2c/base.c b/drivers/gpu/drm/nouveau/core/subdev/i2c/base.c
index 09ba2cc..a652caf 100644
--- a/drivers/gpu/drm/nouveau/core/subdev/i2c/base.c
+++ b/drivers/gpu/drm/nouveau/core/subdev/i2c/base.c
@@ -326,9 +326,9 @@
 }
 
 static void
-nouveau_i2c_intr_disable(struct nouveau_event *event, int type, int index)
+nouveau_i2c_intr_fini(struct nvkm_event *event, int type, int index)
 {
-	struct nouveau_i2c *i2c = nouveau_i2c(event->priv);
+	struct nouveau_i2c *i2c = container_of(event, typeof(*i2c), event);
 	struct nouveau_i2c_port *port = i2c->find(i2c, index);
 	const struct nouveau_i2c_impl *impl = (void *)nv_object(i2c)->oclass;
 	if (port && port->aux >= 0)
@@ -336,15 +336,28 @@
 }
 
 static void
-nouveau_i2c_intr_enable(struct nouveau_event *event, int type, int index)
+nouveau_i2c_intr_init(struct nvkm_event *event, int type, int index)
 {
-	struct nouveau_i2c *i2c = nouveau_i2c(event->priv);
+	struct nouveau_i2c *i2c = container_of(event, typeof(*i2c), event);
 	struct nouveau_i2c_port *port = i2c->find(i2c, index);
 	const struct nouveau_i2c_impl *impl = (void *)nv_object(i2c)->oclass;
 	if (port && port->aux >= 0)
 		impl->aux_mask(i2c, type, 1 << port->aux, 1 << port->aux);
 }
 
+static int
+nouveau_i2c_intr_ctor(void *data, u32 size, struct nvkm_notify *notify)
+{
+	struct nvkm_i2c_ntfy_req *req = data;
+	if (!WARN_ON(size != sizeof(*req))) {
+		notify->size  = sizeof(struct nvkm_i2c_ntfy_rep);
+		notify->types = req->mask;
+		notify->index = req->port;
+		return 0;
+	}
+	return -EINVAL;
+}
+
 static void
 nouveau_i2c_intr(struct nouveau_subdev *subdev)
 {
@@ -364,13 +377,26 @@
 				if (lo & (1 << port->aux)) e |= NVKM_I2C_UNPLUG;
 				if (rq & (1 << port->aux)) e |= NVKM_I2C_IRQ;
 				if (tx & (1 << port->aux)) e |= NVKM_I2C_DONE;
-
-				nouveau_event_trigger(i2c->ntfy, e, port->index);
+				if (e) {
+					struct nvkm_i2c_ntfy_rep rep = {
+						.mask = e,
+					};
+					nvkm_event_send(&i2c->event, rep.mask,
+							port->index, &rep,
+							sizeof(rep));
+				}
 			}
 		}
 	}
 }
 
+static const struct nvkm_event_func
+nouveau_i2c_intr_func = {
+	.ctor = nouveau_i2c_intr_ctor,
+	.init = nouveau_i2c_intr_init,
+	.fini = nouveau_i2c_intr_fini,
+};
+
 int
 _nouveau_i2c_fini(struct nouveau_object *object, bool suspend)
 {
@@ -431,7 +457,7 @@
 	struct nouveau_i2c *i2c = (void *)object;
 	struct nouveau_i2c_port *port, *temp;
 
-	nouveau_event_destroy(&i2c->ntfy);
+	nvkm_event_fini(&i2c->event);
 
 	list_for_each_entry_safe(port, temp, &i2c->ports, head) {
 		nouveau_object_ref(NULL, (struct nouveau_object **)&port);
@@ -547,13 +573,10 @@
 		}
 	}
 
-	ret = nouveau_event_create(4, index, &i2c->ntfy);
+	ret = nvkm_event_init(&nouveau_i2c_intr_func, 4, index, &i2c->event);
 	if (ret)
 		return ret;
 
-	i2c->ntfy->priv = i2c;
-	i2c->ntfy->enable = nouveau_i2c_intr_enable;
-	i2c->ntfy->disable = nouveau_i2c_intr_disable;
 	return 0;
 }
 
diff --git a/drivers/gpu/drm/nouveau/nouveau_connector.c b/drivers/gpu/drm/nouveau/nouveau_connector.c
index dbdc9ad..3f1db28 100644
--- a/drivers/gpu/drm/nouveau/nouveau_connector.c
+++ b/drivers/gpu/drm/nouveau/nouveau_connector.c
@@ -46,6 +46,8 @@
 #include <subdev/gpio.h>
 #include <engine/disp.h>
 
+#include <nvif/event.h>
+
 MODULE_PARM_DESC(tv_disable, "Disable TV-out detection");
 static int nouveau_tv_disable = 0;
 module_param_named(tv_disable, nouveau_tv_disable, int, 0400);
@@ -102,7 +104,7 @@
 nouveau_connector_destroy(struct drm_connector *connector)
 {
 	struct nouveau_connector *nv_connector = nouveau_connector(connector);
-	nouveau_event_ref(NULL, &nv_connector->hpd);
+	nvkm_notify_fini(&nv_connector->hpd);
 	kfree(nv_connector->edid);
 	drm_connector_unregister(connector);
 	drm_connector_cleanup(connector);
@@ -939,18 +941,19 @@
 	.force = nouveau_connector_force
 };
 
-static void
-nouveau_connector_hotplug_work(struct work_struct *work)
+static int
+nouveau_connector_hotplug(struct nvkm_notify *notify)
 {
 	struct nouveau_connector *nv_connector =
-		container_of(work, typeof(*nv_connector), work);
+		container_of(notify, typeof(*nv_connector), hpd);
 	struct drm_connector *connector = &nv_connector->base;
 	struct nouveau_drm *drm = nouveau_drm(connector->dev);
+	const struct nvif_notify_conn_rep_v0 *rep = notify->data;
 	const char *name = connector->name;
 
-	if (nv_connector->status & NVKM_HPD_IRQ) {
+	if (rep->mask & NVIF_NOTIFY_CONN_V0_IRQ) {
 	} else {
-		bool plugged = (nv_connector->status != NVKM_HPD_UNPLUG);
+		bool plugged = (rep->mask != NVIF_NOTIFY_CONN_V0_UNPLUG);
 
 		NV_DEBUG(drm, "%splugged %s\n", plugged ? "" : "un", name);
 
@@ -961,16 +964,7 @@
 		drm_helper_hpd_irq_event(connector->dev);
 	}
 
-	nouveau_event_get(nv_connector->hpd);
-}
-
-static int
-nouveau_connector_hotplug(void *data, u32 type, int index)
-{
-	struct nouveau_connector *nv_connector = data;
-	nv_connector->status = type;
-	schedule_work(&nv_connector->work);
-	return NVKM_EVENT_DROP;
+	return NVKM_NOTIFY_KEEP;
 }
 
 static ssize_t
@@ -1226,16 +1220,19 @@
 		break;
 	}
 
-	ret = nouveau_event_new(pdisp->hpd, NVKM_HPD, index,
-				nouveau_connector_hotplug,
-				nv_connector, &nv_connector->hpd);
+	ret = nvkm_notify_init(&pdisp->hpd, nouveau_connector_hotplug, true,
+			       &(struct nvif_notify_conn_req_v0) {
+				.mask = NVIF_NOTIFY_CONN_V0_ANY,
+				.conn = index,
+			       },
+			       sizeof(struct nvif_notify_conn_req_v0),
+			       sizeof(struct nvif_notify_conn_rep_v0),
+			       &nv_connector->hpd);
 	if (ret)
 		connector->polled = DRM_CONNECTOR_POLL_CONNECT;
 	else
 		connector->polled = DRM_CONNECTOR_POLL_HPD;
 
-	INIT_WORK(&nv_connector->work, nouveau_connector_hotplug_work);
-
 	drm_connector_register(connector);
 	return connector;
 }
diff --git a/drivers/gpu/drm/nouveau/nouveau_connector.h b/drivers/gpu/drm/nouveau/nouveau_connector.h
index 8861b6c..0dcec02 100644
--- a/drivers/gpu/drm/nouveau/nouveau_connector.h
+++ b/drivers/gpu/drm/nouveau/nouveau_connector.h
@@ -67,9 +67,7 @@
 	u8 index;
 	u8 *dcb;
 
-	struct nouveau_eventh *hpd;
-	u32 status;
-	struct work_struct work;
+	struct nvkm_notify hpd;
 
 	struct drm_dp_aux aux;
 
diff --git a/drivers/gpu/drm/nouveau/nouveau_crtc.h b/drivers/gpu/drm/nouveau/nouveau_crtc.h
index 6af9b58..6358640 100644
--- a/drivers/gpu/drm/nouveau/nouveau_crtc.h
+++ b/drivers/gpu/drm/nouveau/nouveau_crtc.h
@@ -31,7 +31,7 @@
 	struct drm_crtc base;
 
 	int index;
-	struct nouveau_eventh *vblank;
+	struct nvkm_notify vblank;
 
 	uint32_t dpms_saved_fp_control;
 	uint32_t fp_users;
diff --git a/drivers/gpu/drm/nouveau/nouveau_display.c b/drivers/gpu/drm/nouveau/nouveau_display.c
index a1247f2..ff43b41 100644
--- a/drivers/gpu/drm/nouveau/nouveau_display.c
+++ b/drivers/gpu/drm/nouveau/nouveau_display.c
@@ -40,13 +40,15 @@
 #include <engine/disp.h>
 
 #include <core/class.h>
+#include <nvif/event.h>
 
 static int
-nouveau_display_vblank_handler(void *data, u32 type, int head)
+nouveau_display_vblank_handler(struct nvkm_notify *notify)
 {
-	struct nouveau_crtc *nv_crtc = data;
+	struct nouveau_crtc *nv_crtc =
+		container_of(notify, typeof(*nv_crtc), vblank);
 	drm_handle_vblank(nv_crtc->base.dev, nv_crtc->index);
-	return NVKM_EVENT_KEEP;
+	return NVKM_NOTIFY_KEEP;
 }
 
 int
@@ -56,7 +58,7 @@
 	list_for_each_entry(crtc, &dev->mode_config.crtc_list, head) {
 		struct nouveau_crtc *nv_crtc = nouveau_crtc(crtc);
 		if (nv_crtc->index == head) {
-			nouveau_event_get(nv_crtc->vblank);
+			nvkm_notify_get(&nv_crtc->vblank);
 			return 0;
 		}
 	}
@@ -70,7 +72,7 @@
 	list_for_each_entry(crtc, &dev->mode_config.crtc_list, head) {
 		struct nouveau_crtc *nv_crtc = nouveau_crtc(crtc);
 		if (nv_crtc->index == head) {
-			nouveau_event_put(nv_crtc->vblank);
+			nvkm_notify_put(&nv_crtc->vblank);
 			return;
 		}
 	}
@@ -165,7 +167,7 @@
 
 	list_for_each_entry(crtc, &dev->mode_config.crtc_list, head) {
 		struct nouveau_crtc *nv_crtc = nouveau_crtc(crtc);
-		nouveau_event_ref(NULL, &nv_crtc->vblank);
+		nvkm_notify_fini(&nv_crtc->vblank);
 	}
 }
 
@@ -179,9 +181,14 @@
 
 	list_for_each_entry(crtc, &dev->mode_config.crtc_list, head) {
 		struct nouveau_crtc *nv_crtc = nouveau_crtc(crtc);
-		ret = nouveau_event_new(pdisp->vblank, 1, nv_crtc->index,
-					nouveau_display_vblank_handler,
-					nv_crtc, &nv_crtc->vblank);
+		ret = nvkm_notify_init(&pdisp->vblank,
+				       nouveau_display_vblank_handler, false,
+				       &(struct nvif_notify_head_req_v0) {
+					.head = nv_crtc->index,
+				       },
+				       sizeof(struct nvif_notify_head_req_v0),
+				       sizeof(struct nvif_notify_head_rep_v0),
+				       &nv_crtc->vblank);
 		if (ret) {
 			nouveau_display_vblank_fini(dev);
 			return ret;
@@ -359,7 +366,7 @@
 	/* enable hotplug interrupts */
 	list_for_each_entry(connector, &dev->mode_config.connector_list, head) {
 		struct nouveau_connector *conn = nouveau_connector(connector);
-		if (conn->hpd) nouveau_event_get(conn->hpd);
+		nvkm_notify_get(&conn->hpd);
 	}
 
 	return ret;
@@ -379,7 +386,7 @@
 	/* disable hotplug interrupts */
 	list_for_each_entry(connector, &dev->mode_config.connector_list, head) {
 		struct nouveau_connector *conn = nouveau_connector(connector);
-		if (conn->hpd) nouveau_event_put(conn->hpd);
+		nvkm_notify_put(&conn->hpd);
 	}
 
 	drm_kms_helper_poll_disable(dev);
diff --git a/drivers/gpu/drm/nouveau/nouveau_fence.c b/drivers/gpu/drm/nouveau/nouveau_fence.c
index ab5ea3b..c5efe25 100644
--- a/drivers/gpu/drm/nouveau/nouveau_fence.c
+++ b/drivers/gpu/drm/nouveau/nouveau_fence.c
@@ -165,12 +165,18 @@
 	return !fence->channel;
 }
 
+struct nouveau_fence_wait {
+	struct nouveau_fence_priv *priv;
+	struct nvkm_notify notify;
+};
+
 static int
-nouveau_fence_wait_uevent_handler(void *data, u32 type, int index)
+nouveau_fence_wait_uevent_handler(struct nvkm_notify *notify)
 {
-	struct nouveau_fence_priv *priv = data;
-	wake_up_all(&priv->waiting);
-	return NVKM_EVENT_KEEP;
+	struct nouveau_fence_wait *wait =
+		container_of(notify, typeof(*wait), notify);
+	wake_up_all(&wait->priv->waiting);
+	return NVKM_NOTIFY_KEEP;
 }
 
 static int
@@ -180,16 +186,16 @@
 	struct nouveau_channel *chan = fence->channel;
 	struct nouveau_fifo *pfifo = nouveau_fifo(chan->drm->device);
 	struct nouveau_fence_priv *priv = chan->drm->fence;
-	struct nouveau_eventh *handler;
+	struct nouveau_fence_wait wait = { .priv = priv };
 	int ret = 0;
 
-	ret = nouveau_event_new(pfifo->uevent, 1, 0,
-				nouveau_fence_wait_uevent_handler,
-				priv, &handler);
+	ret = nvkm_notify_init(&pfifo->uevent,
+			       nouveau_fence_wait_uevent_handler, false,
+			       NULL, 0, 0, &wait.notify);
 	if (ret)
 		return ret;
 
-	nouveau_event_get(handler);
+	nvkm_notify_get(&wait.notify);
 
 	if (fence->timeout) {
 		unsigned long timeout = fence->timeout - jiffies;
@@ -221,7 +227,7 @@
 		}
 	}
 
-	nouveau_event_ref(NULL, &handler);
+	nvkm_notify_fini(&wait.notify);
 	if (unlikely(ret < 0))
 		return ret;
 
diff --git a/drivers/gpu/drm/nouveau/nvif/event.h b/drivers/gpu/drm/nouveau/nvif/event.h
new file mode 100644
index 0000000..6daced0
--- /dev/null
+++ b/drivers/gpu/drm/nouveau/nvif/event.h
@@ -0,0 +1,28 @@
+#ifndef __NVIF_EVENT_H__
+#define __NVIF_EVENT_H__
+
+struct nvif_notify_head_req_v0 {
+	__u8  version;
+	__u8  head;
+};
+
+struct nvif_notify_head_rep_v0 {
+	__u8  version;
+};
+
+struct nvif_notify_conn_req_v0 {
+	__u8  version;
+#define NVIF_NOTIFY_CONN_V0_PLUG                                           0x01
+#define NVIF_NOTIFY_CONN_V0_UNPLUG                                         0x02
+#define NVIF_NOTIFY_CONN_V0_IRQ                                            0x04
+#define NVIF_NOTIFY_CONN_V0_ANY                                            0x07
+	__u8  mask;
+	__u8  conn;
+};
+
+struct nvif_notify_conn_rep_v0 {
+	__u8  version;
+	__u8  mask;
+};
+
+#endif
diff --git a/drivers/gpu/drm/nouveau/nvif/unpack.h b/drivers/gpu/drm/nouveau/nvif/unpack.h
new file mode 100644
index 0000000..5933188
--- /dev/null
+++ b/drivers/gpu/drm/nouveau/nvif/unpack.h
@@ -0,0 +1,24 @@
+#ifndef __NVIF_UNPACK_H__
+#define __NVIF_UNPACK_H__
+
+#define nvif_unvers(d) ({                                                      \
+	ret = (size == sizeof(d)) ? 0 : -ENOSYS;                               \
+	(ret == 0);                                                            \
+})
+
+#define nvif_unpack(d,vl,vh,m) ({                                              \
+	if ((vl) == 0 || ret == -ENOSYS) {                                     \
+		int _size = sizeof(d);                                         \
+		if (_size <= size && (d).version >= (vl) &&                    \
+				     (d).version <= (vh)) {                    \
+			data = (u8 *)data + _size;                             \
+			size = size - _size;                                   \
+			ret = ((m) || !size) ? 0 : -E2BIG;                     \
+		} else {                                                       \
+			ret = -ENOSYS;                                         \
+		}                                                              \
+	}                                                                      \
+	(ret == 0);                                                            \
+})
+
+#endif