From e7e3837f6395e44b5b5fb7cdbcccaba5baf76803 Mon Sep 17 00:00:00 2001
From: Ben Skeggs <bskeggs@redhat.com>
Date: Fri, 22 Oct 2010 10:26:24 +1000
Subject: [PATCH] drm-nouveau-nv86-bug

drm/nv50: implement possible workaround for NV86 PGRAPH TLB flush hang

Signed-off-by: Ben Skeggs <bskeggs@redhat.com>
---
 drivers/gpu/drm/nouveau/nouveau_drv.h   |    5 +++
 drivers/gpu/drm/nouveau/nouveau_mem.c   |   14 +++-----
 drivers/gpu/drm/nouveau/nouveau_sgdma.c |    8 ++--
 drivers/gpu/drm/nouveau/nouveau_state.c |   10 ++++++
 drivers/gpu/drm/nouveau/nv50_fifo.c     |    5 +++
 drivers/gpu/drm/nouveau/nv50_graph.c    |   52 +++++++++++++++++++++++++++++++
 drivers/gpu/drm/nouveau/nv50_instmem.c  |    1 -
 7 files changed, 82 insertions(+), 13 deletions(-)

diff --git a/drivers/gpu/drm/nouveau/nouveau_drv.h b/drivers/gpu/drm/nouveau/nouveau_drv.h
index be53e92..4273390 100644
--- a/drivers/gpu/drm/nouveau/nouveau_drv.h
+++ b/drivers/gpu/drm/nouveau/nouveau_drv.h
@@ -304,6 +304,7 @@ struct nouveau_fifo_engine {
 	void (*destroy_context)(struct nouveau_channel *);
 	int  (*load_context)(struct nouveau_channel *);
 	int  (*unload_context)(struct drm_device *);
+	void (*tlb_flush)(struct drm_device *dev);
 };
 
 struct nouveau_pgraph_object_method {
@@ -336,6 +337,7 @@ struct nouveau_pgraph_engine {
 	void (*destroy_context)(struct nouveau_channel *);
 	int  (*load_context)(struct nouveau_channel *);
 	int  (*unload_context)(struct drm_device *);
+	void (*tlb_flush)(struct drm_device *dev);
 
 	void (*set_region_tiling)(struct drm_device *dev, int i, uint32_t addr,
 				  uint32_t size, uint32_t pitch);
@@ -944,6 +946,7 @@ extern int  nv50_fifo_create_context(struct nouveau_channel *);
 extern void nv50_fifo_destroy_context(struct nouveau_channel *);
 extern int  nv50_fifo_load_context(struct nouveau_channel *);
 extern int  nv50_fifo_unload_context(struct drm_device *);
+extern void nv50_fifo_tlb_flush(struct drm_device *dev);
 
 /* nvc0_fifo.c */
 extern int  nvc0_fifo_init(struct drm_device *);
@@ -1021,6 +1024,8 @@ extern int  nv50_graph_load_context(struct nouveau_channel *);
 extern int  nv50_graph_unload_context(struct drm_device *);
 extern void nv50_graph_context_switch(struct drm_device *);
 extern int  nv50_grctx_init(struct nouveau_grctx *);
+extern void nv50_graph_tlb_flush(struct drm_device *dev);
+extern void nv86_graph_tlb_flush(struct drm_device *dev);
 
 /* nvc0_graph.c */
 extern int  nvc0_graph_init(struct drm_device *);
diff --git a/drivers/gpu/drm/nouveau/nouveau_mem.c b/drivers/gpu/drm/nouveau/nouveau_mem.c
index 4f0ae39..514ad9f 100644
--- a/drivers/gpu/drm/nouveau/nouveau_mem.c
+++ b/drivers/gpu/drm/nouveau/nouveau_mem.c
@@ -173,11 +173,10 @@ nv50_mem_vm_bind_linear(struct drm_device *dev, uint64_t virt, uint32_t size,
 			}
 		}
 	}
-	dev_priv->engine.instmem.flush(dev);
 
-	nv50_vm_flush(dev, 5);
-	nv50_vm_flush(dev, 0);
-	nv50_vm_flush(dev, 4);
+	dev_priv->engine.instmem.flush(dev);
+	dev_priv->engine.fifo.tlb_flush(dev);
+	dev_priv->engine.graph.tlb_flush(dev);
 	nv50_vm_flush(dev, 6);
 	return 0;
 }
@@ -207,11 +206,10 @@ nv50_mem_vm_unbind(struct drm_device *dev, uint64_t virt, uint32_t size)
 			pte++;
 		}
 	}
-	dev_priv->engine.instmem.flush(dev);
 
-	nv50_vm_flush(dev, 5);
-	nv50_vm_flush(dev, 0);
-	nv50_vm_flush(dev, 4);
+	dev_priv->engine.instmem.flush(dev);
+	dev_priv->engine.fifo.tlb_flush(dev);
+	dev_priv->engine.graph.tlb_flush(dev);
 	nv50_vm_flush(dev, 6);
 }
 
diff --git a/drivers/gpu/drm/nouveau/nouveau_sgdma.c b/drivers/gpu/drm/nouveau/nouveau_sgdma.c
index 7f028fe..d55a583 100644
--- a/drivers/gpu/drm/nouveau/nouveau_sgdma.c
+++ b/drivers/gpu/drm/nouveau/nouveau_sgdma.c
@@ -120,8 +120,8 @@ nouveau_sgdma_bind(struct ttm_backend *be, struct ttm_mem_reg *mem)
 	dev_priv->engine.instmem.flush(nvbe->dev);
 
 	if (dev_priv->card_type == NV_50) {
-		nv50_vm_flush(dev, 5); /* PGRAPH */
-		nv50_vm_flush(dev, 0); /* PFIFO */
+		dev_priv->engine.fifo.tlb_flush(dev);
+		dev_priv->engine.graph.tlb_flush(dev);
 	}
 
 	nvbe->bound = true;
@@ -162,8 +162,8 @@ nouveau_sgdma_unbind(struct ttm_backend *be)
 	dev_priv->engine.instmem.flush(nvbe->dev);
 
 	if (dev_priv->card_type == NV_50) {
-		nv50_vm_flush(dev, 5);
-		nv50_vm_flush(dev, 0);
+		dev_priv->engine.fifo.tlb_flush(dev);
+		dev_priv->engine.graph.tlb_flush(dev);
 	}
 
 	nvbe->bound = false;
diff --git a/drivers/gpu/drm/nouveau/nouveau_state.c b/drivers/gpu/drm/nouveau/nouveau_state.c
index be85960..47c353c 100644
--- a/drivers/gpu/drm/nouveau/nouveau_state.c
+++ b/drivers/gpu/drm/nouveau/nouveau_state.c
@@ -333,6 +333,15 @@ static int nouveau_init_engine_ptrs(struct drm_device *dev)
 		engine->graph.destroy_context	= nv50_graph_destroy_context;
 		engine->graph.load_context	= nv50_graph_load_context;
 		engine->graph.unload_context	= nv50_graph_unload_context;
+		if (dev_priv->chipset != 0x86)
+			engine->graph.tlb_flush	= nv50_graph_tlb_flush;
+		else {
+			/* from what i can see nvidia do this on every
+			 * pre-NVA3 board except NVAC, but, we've only
+			 * ever seen problems on NV86
+			 */
+			engine->graph.tlb_flush	= nv86_graph_tlb_flush;
+		}
 		engine->fifo.channels		= 128;
 		engine->fifo.init		= nv50_fifo_init;
 		engine->fifo.takedown		= nv50_fifo_takedown;
@@ -344,6 +353,7 @@ static int nouveau_init_engine_ptrs(struct drm_device *dev)
 		engine->fifo.destroy_context	= nv50_fifo_destroy_context;
 		engine->fifo.load_context	= nv50_fifo_load_context;
 		engine->fifo.unload_context	= nv50_fifo_unload_context;
+		engine->fifo.tlb_flush		= nv50_fifo_tlb_flush;
 		engine->display.early_init	= nv50_display_early_init;
 		engine->display.late_takedown	= nv50_display_late_takedown;
 		engine->display.create		= nv50_display_create;
diff --git a/drivers/gpu/drm/nouveau/nv50_fifo.c b/drivers/gpu/drm/nouveau/nv50_fifo.c
index a46a961..1da65bd 100644
--- a/drivers/gpu/drm/nouveau/nv50_fifo.c
+++ b/drivers/gpu/drm/nouveau/nv50_fifo.c
@@ -464,3 +464,8 @@ nv50_fifo_unload_context(struct drm_device *dev)
 	return 0;
 }
 
+void
+nv50_fifo_tlb_flush(struct drm_device *dev)
+{
+	nv50_vm_flush(dev, 5);
+}
diff --git a/drivers/gpu/drm/nouveau/nv50_graph.c b/drivers/gpu/drm/nouveau/nv50_graph.c
index cbf5ae2..8b669d0 100644
--- a/drivers/gpu/drm/nouveau/nv50_graph.c
+++ b/drivers/gpu/drm/nouveau/nv50_graph.c
@@ -402,3 +402,55 @@ struct nouveau_pgraph_object_class nv50_graph_grclass[] = {
 	{ 0x8597, false, NULL }, /* tesla (nva3, nva5, nva8) */
 	{}
 };
+
+void
+nv50_graph_tlb_flush(struct drm_device *dev)
+{
+	nv50_vm_flush(dev, 0);
+}
+
+void
+nv86_graph_tlb_flush(struct drm_device *dev)
+{
+	struct drm_nouveau_private *dev_priv = dev->dev_private;
+	struct nouveau_timer_engine *ptimer = &dev_priv->engine.timer;
+	bool idle, timeout = false;
+	unsigned long flags;
+	u64 start;
+	u32 tmp;
+
+	spin_lock_irqsave(&dev_priv->context_switch_lock, flags);
+	nv_mask(dev, 0x400500, 0x00000001, 0x00000000);
+
+	start = ptimer->read(dev);
+	do {
+		idle = true;
+
+		for (tmp = nv_rd32(dev, 0x400380); tmp && idle; tmp >>= 3) {
+			if ((tmp & 7) == 1)
+				idle = false;
+		}
+
+		for (tmp = nv_rd32(dev, 0x400384); tmp && idle; tmp >>= 3) {
+			if ((tmp & 7) == 1)
+				idle = false;
+		}
+
+		for (tmp = nv_rd32(dev, 0x400388); tmp && idle; tmp >>= 3) {
+			if ((tmp & 7) == 1)
+				idle = false;
+		}
+	} while (!idle && !(timeout = ptimer->read(dev) - start > 2000000000));
+
+	if (timeout) {
+		NV_ERROR(dev, "PGRAPH TLB flush idle timeout fail: "
+			      "0x%08x 0x%08x 0x%08x 0x%08x\n",
+			 nv_rd32(dev, 0x400700), nv_rd32(dev, 0x400380),
+			 nv_rd32(dev, 0x400384), nv_rd32(dev, 0x400388));
+	}
+
+	nv50_vm_flush(dev, 0);
+
+	nv_mask(dev, 0x400500, 0x00000001, 0x00000001);
+	spin_unlock_irqrestore(&dev_priv->context_switch_lock, flags);
+}
diff --git a/drivers/gpu/drm/nouveau/nv50_instmem.c b/drivers/gpu/drm/nouveau/nv50_instmem.c
index ac3de05..c836ddc 100644
--- a/drivers/gpu/drm/nouveau/nv50_instmem.c
+++ b/drivers/gpu/drm/nouveau/nv50_instmem.c
@@ -402,7 +402,6 @@ nv50_instmem_bind(struct drm_device *dev, struct nouveau_gpuobj *gpuobj)
 	}
 	dev_priv->engine.instmem.flush(dev);
 
-	nv50_vm_flush(dev, 4);
 	nv50_vm_flush(dev, 6);
 
 	gpuobj->im_bound = 1;
-- 
1.7.3.1

