Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

drm/nouveau/tmr: calibrate for ns timestamps on init

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>

+86 -30
+86 -30
drivers/gpu/drm/nouveau/nv04_timer.c
··· 3 3 #include "nouveau_drv.h" 4 4 #include "nouveau_drm.h" 5 5 6 - int 7 - nv04_timer_init(struct drm_device *dev) 6 + static u32 7 + nv04_crystal_freq(struct drm_device *dev) 8 8 { 9 - nv_wr32(dev, NV04_PTIMER_INTR_EN_0, 0x00000000); 10 - nv_wr32(dev, NV04_PTIMER_INTR_0, 0xFFFFFFFF); 9 + struct drm_nouveau_private *dev_priv = dev->dev_private; 10 + u32 extdev_boot0 = nv_rd32(dev, 0x101000); 11 + int type; 11 12 12 - /* Just use the pre-existing values when possible for now; these regs 13 - * are not written in nv (driver writer missed a /4 on the address), and 14 - * writing 8 and 3 to the correct regs breaks the timings on the LVDS 15 - * hardware sequencing microcode. 16 - * A correct solution (involving calculations with the GPU PLL) can 17 - * be done when kernel modesetting lands 18 - */ 19 - if (!nv_rd32(dev, NV04_PTIMER_NUMERATOR) || 20 - !nv_rd32(dev, NV04_PTIMER_DENOMINATOR)) { 21 - nv_wr32(dev, NV04_PTIMER_NUMERATOR, 0x00000008); 22 - nv_wr32(dev, NV04_PTIMER_DENOMINATOR, 0x00000003); 13 + type = !!(extdev_boot0 & 0x00000040); 14 + if ((dev_priv->chipset >= 0x17 && dev_priv->chipset < 0x20) || 15 + dev_priv->chipset >= 0x25) 16 + type |= (extdev_boot0 & 0x00400000) ? 2 : 0; 17 + 18 + switch (type) { 19 + case 0: return 13500000; 20 + case 1: return 14318180; 21 + case 2: return 27000000; 22 + case 3: return 25000000; 23 + default: 24 + break; 23 25 } 24 26 25 27 return 0; 26 28 } 27 29 28 - uint64_t 30 + int 31 + nv04_timer_init(struct drm_device *dev) 32 + { 33 + struct drm_nouveau_private *dev_priv = dev->dev_private; 34 + u32 m, n, d; 35 + 36 + nv_wr32(dev, NV04_PTIMER_INTR_EN_0, 0x00000000); 37 + nv_wr32(dev, NV04_PTIMER_INTR_0, 0xFFFFFFFF); 38 + 39 + /* aim for 31.25MHz, which gives us nanosecond timestamps */ 40 + d = 1000000000 / 32; 41 + 42 + /* determine base clock for timer source */ 43 + if (dev_priv->chipset < 0x40) { 44 + n = dev_priv->engine.pm.clock_get(dev, PLL_CORE); 45 + } else 46 + if (dev_priv->chipset == 0x40) { 47 + /*XXX: figure this out */ 48 + n = 0; 49 + } else { 50 + n = nv04_crystal_freq(dev); 51 + m = 1; 52 + while (n < (d * 2)) { 53 + n += (n / m); 54 + m++; 55 + } 56 + 57 + nv_wr32(dev, 0x009220, m - 1); 58 + } 59 + 60 + if (!n) { 61 + NV_WARN(dev, "PTIMER: unknown input clock freq\n"); 62 + if (!nv_rd32(dev, NV04_PTIMER_NUMERATOR) || 63 + !nv_rd32(dev, NV04_PTIMER_DENOMINATOR)) { 64 + nv_wr32(dev, NV04_PTIMER_NUMERATOR, 1); 65 + nv_wr32(dev, NV04_PTIMER_DENOMINATOR, 1); 66 + } 67 + return 0; 68 + } 69 + 70 + /* reduce ratio to acceptable values */ 71 + while (((n % 5) == 0) && ((d % 5) == 0)) { 72 + n /= 5; 73 + d /= 5; 74 + } 75 + 76 + while (((n % 2) == 0) && ((d % 2) == 0)) { 77 + n /= 2; 78 + d /= 2; 79 + } 80 + 81 + while (n > 0xffff || d > 0xffff) { 82 + n >>= 1; 83 + d >>= 1; 84 + } 85 + 86 + nv_wr32(dev, NV04_PTIMER_NUMERATOR, n); 87 + nv_wr32(dev, NV04_PTIMER_DENOMINATOR, d); 88 + return 0; 89 + } 90 + 91 + u64 29 92 nv04_timer_read(struct drm_device *dev) 30 93 { 31 - uint32_t low; 32 - /* From kmmio dumps on nv28 this looks like how the blob does this. 33 - * It reads the high dword twice, before and after. 34 - * The only explanation seems to be that the 64-bit timer counter 35 - * advances between high and low dword reads and may corrupt the 36 - * result. Not confirmed. 37 - */ 38 - uint32_t high2 = nv_rd32(dev, NV04_PTIMER_TIME_1); 39 - uint32_t high1; 94 + u32 hi, lo; 95 + 40 96 do { 41 - high1 = high2; 42 - low = nv_rd32(dev, NV04_PTIMER_TIME_0); 43 - high2 = nv_rd32(dev, NV04_PTIMER_TIME_1); 44 - } while (high1 != high2); 45 - return (((uint64_t)high2) << 32) | (uint64_t)low; 97 + hi = nv_rd32(dev, NV04_PTIMER_TIME_1); 98 + lo = nv_rd32(dev, NV04_PTIMER_TIME_0); 99 + } while (hi != nv_rd32(dev, NV04_PTIMER_TIME_1)); 100 + 101 + return ((u64)hi << 32 | lo); 46 102 } 47 103 48 104 void