diff --git a/drivers/gpu/drm/nouveau/core/include/subdev/clock.h b/drivers/gpu/drm/nouveau/core/include/subdev/clock.h
index c01e29c9f89ac023ce24c9229fe1cdf14e5c4c16..3168e1a0578d4fc8ca680a1a848f9f4f1b2f0f14 100644
--- a/drivers/gpu/drm/nouveau/core/include/subdev/clock.h
+++ b/drivers/gpu/drm/nouveau/core/include/subdev/clock.h
@@ -71,6 +71,10 @@ struct nouveau_clock {
 	struct list_head states;
 	int state_nr;
 
+	struct work_struct work;
+	wait_queue_head_t wait;
+	atomic_t waiting;
+
 	int pstate; /* current */
 	int ustate; /* user-requested (-1 disabled, -2 perfmon) */
 	int astate; /* perfmon adjustment (base) */
diff --git a/drivers/gpu/drm/nouveau/core/subdev/clock/base.c b/drivers/gpu/drm/nouveau/core/subdev/clock/base.c
index 22351f594d2aaa72713fbb3d56f5151ec4c34e20..77966370e99590705161da30922246ffb959a6a3 100644
--- a/drivers/gpu/drm/nouveau/core/subdev/clock/base.c
+++ b/drivers/gpu/drm/nouveau/core/subdev/clock/base.c
@@ -194,10 +194,14 @@ nouveau_pstate_prog(struct nouveau_clock *clk, int pstatei)
 	return nouveau_cstate_prog(clk, pstate, 0);
 }
 
-static int
-nouveau_pstate_calc(struct nouveau_clock *clk)
+static void
+nouveau_pstate_work(struct work_struct *work)
 {
-	int pstate, ret = 0;
+	struct nouveau_clock *clk = container_of(work, typeof(*clk), work);
+	int pstate;
+
+	if (!atomic_xchg(&clk->waiting, 0))
+		return;
 
 	nv_trace(clk, "P %d U %d A %d T %d D %d\n", clk->pstate,
 		 clk->ustate, clk->astate, clk->tstate, clk->dstate);
@@ -211,9 +215,25 @@ nouveau_pstate_calc(struct nouveau_clock *clk)
 	}
 
 	nv_trace(clk, "-> %d\n", pstate);
-	if (pstate != clk->pstate)
-		ret = nouveau_pstate_prog(clk, pstate);
-	return ret;
+	if (pstate != clk->pstate) {
+		int ret = nouveau_pstate_prog(clk, pstate);
+		if (ret) {
+			nv_error(clk, "error setting pstate %d: %d\n",
+				 pstate, ret);
+		}
+	}
+
+	wake_up_all(&clk->wait);
+}
+
+static int
+nouveau_pstate_calc(struct nouveau_clock *clk, bool wait)
+{
+	atomic_set(&clk->waiting, 1);
+	schedule_work(&clk->work);
+	if (wait)
+		wait_event(clk->wait, !atomic_read(&clk->waiting));
+	return 0;
 }
 
 static void
@@ -371,7 +391,7 @@ nouveau_clock_ustate(struct nouveau_clock *clk, int req)
 	int ret = nouveau_clock_ustate_update(clk, req);
 	if (ret)
 		return ret;
-	return nouveau_pstate_calc(clk);
+	return nouveau_pstate_calc(clk, true);
 }
 
 int
@@ -381,7 +401,7 @@ nouveau_clock_astate(struct nouveau_clock *clk, int req, int rel)
 	if ( rel) clk->astate += rel;
 	clk->astate = min(clk->astate, clk->state_nr - 1);
 	clk->astate = max(clk->astate, 0);
-	return nouveau_pstate_calc(clk);
+	return nouveau_pstate_calc(clk, true);
 }
 
 int
@@ -391,7 +411,7 @@ nouveau_clock_tstate(struct nouveau_clock *clk, int req, int rel)
 	if ( rel) clk->tstate += rel;
 	clk->tstate = min(clk->tstate, 0);
 	clk->tstate = max(clk->tstate, -(clk->state_nr - 1));
-	return nouveau_pstate_calc(clk);
+	return nouveau_pstate_calc(clk, true);
 }
 
 int
@@ -401,7 +421,7 @@ nouveau_clock_dstate(struct nouveau_clock *clk, int req, int rel)
 	if ( rel) clk->dstate += rel;
 	clk->dstate = min(clk->dstate, clk->state_nr - 1);
 	clk->dstate = max(clk->dstate, 0);
-	return nouveau_pstate_calc(clk);
+	return nouveau_pstate_calc(clk, true);
 }
 
 /******************************************************************************
@@ -434,7 +454,7 @@ _nouveau_clock_init(struct nouveau_object *object)
 	clk->tstate = 0;
 	clk->dstate = 0;
 	clk->pstate = -1;
-	nouveau_pstate_calc(clk);
+	nouveau_pstate_calc(clk, true);
 	return 0;
 }
 
@@ -474,6 +494,10 @@ nouveau_clock_create_(struct nouveau_object *parent,
 	clk->domains = clocks;
 	clk->ustate = -1;
 
+	INIT_WORK(&clk->work, nouveau_pstate_work);
+	init_waitqueue_head(&clk->wait);
+	atomic_set(&clk->waiting, 0);
+
 	idx = 0;
 	do {
 		ret = nouveau_pstate_new(clk, idx++);