drm/nouveau/tmr: calibrate for ns timestamps on init
authorBen Skeggs <bskeggs@redhat.com>
Sun, 3 Jul 2011 11:16:12 +0000 (21:16 +1000)
committerBen Skeggs <bskeggs@redhat.com>
Tue, 20 Sep 2011 06:04:36 +0000 (16:04 +1000)
We previously assumed (incorrectly a lot of the time) that PTIMER would
be programmed at a frequency which'd give its 64-bit timestamps in
nanoseconds.

By programming PTIMER ourselves, we avoid this problem.

Reviewed-by: Martin Peres <martin.peres@ensi-bourges.fr>
Signed-off-by: Ben Skeggs <bskeggs@redhat.com>
drivers/gpu/drm/nouveau/nv04_timer.c

index 1d09ddd..afb9d4b 100644 (file)
 #include "nouveau_drv.h"
 #include "nouveau_drm.h"
 
+static u32
+nv04_crystal_freq(struct drm_device *dev)
+{
+       struct drm_nouveau_private *dev_priv = dev->dev_private;
+       u32 extdev_boot0 = nv_rd32(dev, 0x101000);
+       int type;
+
+       type = !!(extdev_boot0 & 0x00000040);
+       if ((dev_priv->chipset >= 0x17 && dev_priv->chipset < 0x20) ||
+           dev_priv->chipset >= 0x25)
+               type |= (extdev_boot0 & 0x00400000) ? 2 : 0;
+
+       switch (type) {
+       case 0: return 13500000;
+       case 1: return 14318180;
+       case 2: return 27000000;
+       case 3: return 25000000;
+       default:
+               break;
+       }
+
+       return 0;
+}
+
 int
 nv04_timer_init(struct drm_device *dev)
 {
+       struct drm_nouveau_private *dev_priv = dev->dev_private;
+       u32 m, n, d;
+
        nv_wr32(dev, NV04_PTIMER_INTR_EN_0, 0x00000000);
        nv_wr32(dev, NV04_PTIMER_INTR_0, 0xFFFFFFFF);
 
-       /* Just use the pre-existing values when possible for now; these regs
-        * are not written in nv (driver writer missed a /4 on the address), and
-        * writing 8 and 3 to the correct regs breaks the timings on the LVDS
-        * hardware sequencing microcode.
-        * A correct solution (involving calculations with the GPU PLL) can
-        * be done when kernel modesetting lands
-        */
-       if (!nv_rd32(dev, NV04_PTIMER_NUMERATOR) ||
-                               !nv_rd32(dev, NV04_PTIMER_DENOMINATOR)) {
-               nv_wr32(dev, NV04_PTIMER_NUMERATOR, 0x00000008);
-               nv_wr32(dev, NV04_PTIMER_DENOMINATOR, 0x00000003);
+       /* aim for 31.25MHz, which gives us nanosecond timestamps */
+       d = 1000000000 / 32;
+
+       /* determine base clock for timer source */
+       if (dev_priv->chipset < 0x40) {
+               n = dev_priv->engine.pm.clock_get(dev, PLL_CORE);
+       } else
+       if (dev_priv->chipset == 0x40) {
+               /*XXX: figure this out */
+               n = 0;
+       } else {
+               n = nv04_crystal_freq(dev);
+               m = 1;
+               while (n < (d * 2)) {
+                       n += (n / m);
+                       m++;
+               }
+
+               nv_wr32(dev, 0x009220, m - 1);
        }
 
+       if (!n) {
+               NV_WARN(dev, "PTIMER: unknown input clock freq\n");
+               if (!nv_rd32(dev, NV04_PTIMER_NUMERATOR) ||
+                   !nv_rd32(dev, NV04_PTIMER_DENOMINATOR)) {
+                       nv_wr32(dev, NV04_PTIMER_NUMERATOR, 1);
+                       nv_wr32(dev, NV04_PTIMER_DENOMINATOR, 1);
+               }
+               return 0;
+       }
+
+       /* reduce ratio to acceptable values */
+       while (((n % 5) == 0) && ((d % 5) == 0)) {
+               n /= 5;
+               d /= 5;
+       }
+
+       while (((n % 2) == 0) && ((d % 2) == 0)) {
+               n /= 2;
+               d /= 2;
+       }
+
+       while (n > 0xffff || d > 0xffff) {
+               n >>= 1;
+               d >>= 1;
+       }
+
+       nv_wr32(dev, NV04_PTIMER_NUMERATOR, n);
+       nv_wr32(dev, NV04_PTIMER_DENOMINATOR, d);
        return 0;
 }
 
-uint64_t
+u64
 nv04_timer_read(struct drm_device *dev)
 {
-       uint32_t low;
-       /* From kmmio dumps on nv28 this looks like how the blob does this.
-        * It reads the high dword twice, before and after.
-        * The only explanation seems to be that the 64-bit timer counter
-        * advances between high and low dword reads and may corrupt the
-        * result. Not confirmed.
-        */
-       uint32_t high2 = nv_rd32(dev, NV04_PTIMER_TIME_1);
-       uint32_t high1;
+       u32 hi, lo;
+
        do {
-               high1 = high2;
-               low = nv_rd32(dev, NV04_PTIMER_TIME_0);
-               high2 = nv_rd32(dev, NV04_PTIMER_TIME_1);
-       } while (high1 != high2);
-       return (((uint64_t)high2) << 32) | (uint64_t)low;
+               hi = nv_rd32(dev, NV04_PTIMER_TIME_1);
+               lo = nv_rd32(dev, NV04_PTIMER_TIME_0);
+       } while (hi != nv_rd32(dev, NV04_PTIMER_TIME_1));
+
+       return ((u64)hi << 32 | lo);
 }
 
 void