return 0;
 }
 
-static void
-nv40_mem_timing_entry(struct drm_device *dev, struct nouveau_pm_tbl_header *hdr,
-                     struct nouveau_pm_tbl_entry *e,
-                     struct nouveau_pm_memtiming *t,
-                     struct nouveau_pm_memtiming *boot)
+static int
+nv40_mem_timing_calc(struct drm_device *dev, u32 freq,
+                    struct nouveau_pm_tbl_entry *e, u8 len,
+                    struct nouveau_pm_memtiming *boot,
+                    struct nouveau_pm_memtiming *t)
 {
-
        t->reg[0] = (e->tRP << 24 | e->tRAS << 16 | e->tRFC << 8 | e->tRC);
 
        /* XXX: I don't trust the -1's and +1's... they must come
 
        NV_DEBUG(dev, "Entry %d: 220: %08x %08x %08x\n", t->id,
                 t->reg[0], t->reg[1], t->reg[2]);
+       return 0;
 }
 
-static void
-nv50_mem_timing_entry(struct drm_device *dev, struct bit_entry *P,
-                     struct nouveau_pm_tbl_header *hdr,
-                     struct nouveau_pm_tbl_entry *e,
-                     struct nouveau_pm_memtiming *t,
-                     struct nouveau_pm_memtiming *boot)
+static int
+nv50_mem_timing_calc(struct drm_device *dev, u32 freq,
+                    struct nouveau_pm_tbl_entry *e, u8 len,
+                    struct nouveau_pm_memtiming *boot,
+                    struct nouveau_pm_memtiming *t)
 {
        struct drm_nouveau_private *dev_priv = dev->dev_private;
+       struct bit_entry P;
        uint8_t unk18 = 1, unk20 = 0, unk21 = 0, tmp7_3;
 
-       switch (min(hdr->entry_len, (u8) 22)) {
+       if (bit_table(dev, 'P', &P))
+               return -EINVAL;
+
+       switch (min(len, (u8) 22)) {
        case 22:
                unk21 = e->tUNK_21;
        case 21:
 
        t->reg[8] = boot->reg[8] & 0xffffff00;
 
-       if (P->version == 1) {
+       if (P.version == 1) {
                t->reg[1] |= (e->tCL + 2 - (t->tCWL - 1));
 
                t->reg[3] = (0x14 + e->tCL) << 24 |
        NV_DEBUG(dev, "         230: %08x %08x %08x %08x\n",
                 t->reg[4], t->reg[5], t->reg[6], t->reg[7]);
        NV_DEBUG(dev, "         240: %08x\n", t->reg[8]);
+       return 0;
 }
 
-static void
-nvc0_mem_timing_entry(struct drm_device *dev, struct nouveau_pm_tbl_header *hdr,
-                     struct nouveau_pm_tbl_entry *e,
-                     struct nouveau_pm_memtiming *t,
-                     struct nouveau_pm_memtiming *boot)
+static int
+nvc0_mem_timing_calc(struct drm_device *dev, u32 freq,
+                    struct nouveau_pm_tbl_entry *e, u8 len,
+                    struct nouveau_pm_memtiming *boot,
+                    struct nouveau_pm_memtiming *t)
 {
        if (e->tCWL > 0)
                t->tCWL = e->tCWL;
        NV_DEBUG(dev, "Entry %d: 290: %08x %08x %08x %08x\n", t->id,
                 t->reg[0], t->reg[1], t->reg[2], t->reg[3]);
        NV_DEBUG(dev, "         2a0: %08x\n", t->reg[4]);
+       return 0;
 }
 
 /**
  * MR generation methods
  */
 
-static bool
-nouveau_mem_ddr2_mr(struct drm_device *dev, struct nouveau_pm_tbl_header *hdr,
-                   struct nouveau_pm_tbl_entry *e,
-                   struct nouveau_pm_memtiming *t,
-                   struct nouveau_pm_memtiming *boot)
+static int
+nouveau_mem_ddr2_mr(struct drm_device *dev, u32 freq,
+                   struct nouveau_pm_tbl_entry *e, u8 len,
+                   struct nouveau_pm_memtiming *boot,
+                   struct nouveau_pm_memtiming *t)
 {
        t->drive_strength = 0;
-       if (hdr->entry_len < 15) {
+       if (len < 15) {
                t->odt = boot->odt;
        } else {
                t->odt = e->RAM_FT1 & 0x07;
 
        if (e->tCL >= NV_MEM_CL_DDR2_MAX) {
                NV_WARN(dev, "(%u) Invalid tCL: %u", t->id, e->tCL);
-               return false;
+               return -ERANGE;
        }
 
        if (e->tWR >= NV_MEM_WR_DDR2_MAX) {
                NV_WARN(dev, "(%u) Invalid tWR: %u", t->id, e->tWR);
-               return false;
+               return -ERANGE;
        }
 
        if (t->odt > 3) {
                   (t->odt & 0x2) << 5;
 
        NV_DEBUG(dev, "(%u) MR: %08x", t->id, t->mr[0]);
-       return true;
+       return 0;
 }
 
 uint8_t nv_mem_wr_lut_ddr3[NV_MEM_WR_DDR3_MAX] = {
        0, 0, 0, 0, 0, 1, 2, 3, 4, 5, 5, 6, 6, 7, 7, 0, 0};
 
-static bool
-nouveau_mem_ddr3_mr(struct drm_device *dev, struct nouveau_pm_tbl_header *hdr,
-                   struct nouveau_pm_tbl_entry *e,
-                   struct nouveau_pm_memtiming *t,
-                   struct nouveau_pm_memtiming *boot)
+static int
+nouveau_mem_ddr3_mr(struct drm_device *dev, u32 freq,
+                   struct nouveau_pm_tbl_entry *e, u8 len,
+                   struct nouveau_pm_memtiming *boot,
+                   struct nouveau_pm_memtiming *t)
 {
        u8 cl = e->tCL - 4;
 
        t->drive_strength = 0;
-       if (hdr->entry_len < 15) {
+       if (len < 15) {
                t->odt = boot->odt;
        } else {
                t->odt = e->RAM_FT1 & 0x07;
 
        if (e->tCL >= NV_MEM_CL_DDR3_MAX || e->tCL < 4) {
                NV_WARN(dev, "(%u) Invalid tCL: %u", t->id, e->tCL);
-               return false;
+               return -ERANGE;
        }
 
        if (e->tWR >= NV_MEM_WR_DDR3_MAX || e->tWR < 4) {
                NV_WARN(dev, "(%u) Invalid tWR: %u", t->id, e->tWR);
-               return false;
+               return -ERANGE;
        }
 
        if (e->tCWL < 5) {
                NV_WARN(dev, "(%u) Invalid tCWL: %u", t->id, e->tCWL);
-               return false;
+               return -ERANGE;
        }
 
        t->mr[0] = (boot->mr[0] & 0x180b) |
        t->mr[2] = (boot->mr[2] & 0x20ffb7) | (e->tCWL - 5) << 3;
 
        NV_DEBUG(dev, "(%u) MR: %08x %08x", t->id, t->mr[0], t->mr[2]);
-       return true;
+       return 0;
 }
 
 uint8_t nv_mem_cl_lut_gddr3[NV_MEM_CL_GDDR3_MAX] = {
 uint8_t nv_mem_wr_lut_gddr3[NV_MEM_WR_GDDR3_MAX] = {
        0, 0, 0, 0, 0, 2, 3, 8, 9, 10, 11, 0, 0, 1, 1, 0, 3};
 
-static bool
-nouveau_mem_gddr3_mr(struct drm_device *dev, struct nouveau_pm_tbl_header *hdr,
-                    struct nouveau_pm_tbl_entry *e,
-                    struct nouveau_pm_memtiming *t,
-                    struct nouveau_pm_memtiming *boot)
+static int
+nouveau_mem_gddr3_mr(struct drm_device *dev, u32 freq,
+                    struct nouveau_pm_tbl_entry *e, u8 len,
+                    struct nouveau_pm_memtiming *boot,
+                    struct nouveau_pm_memtiming *t)
 {
-       if (hdr->entry_len < 15) {
+       if (len < 15) {
                t->drive_strength = boot->drive_strength;
                t->odt = boot->odt;
        } else {
 
        if (e->tCL >= NV_MEM_CL_GDDR3_MAX) {
                NV_WARN(dev, "(%u) Invalid tCL: %u", t->id, e->tCL);
-               return false;
+               return -ERANGE;
        }
 
        if (e->tWR >= NV_MEM_WR_GDDR3_MAX) {
                NV_WARN(dev, "(%u) Invalid tWR: %u", t->id, e->tWR);
-               return false;
+               return -ERANGE;
        }
 
        if (t->odt > 3) {
                   (nv_mem_wr_lut_gddr3[e->tWR] & 0xf) << 4;
 
        NV_DEBUG(dev, "(%u) MR: %08x %08x", t->id, t->mr[0], t->mr[1]);
-       return true;
+       return 0;
 }
 
-static bool
-nouveau_mem_gddr5_mr(struct drm_device *dev, struct nouveau_pm_tbl_header *hdr,
-                    struct nouveau_pm_tbl_entry *e,
-                    struct nouveau_pm_memtiming *t,
-                    struct nouveau_pm_memtiming *boot)
+static int
+nouveau_mem_gddr5_mr(struct drm_device *dev, u32 freq,
+                    struct nouveau_pm_tbl_entry *e, u8 len,
+                    struct nouveau_pm_memtiming *boot,
+                    struct nouveau_pm_memtiming *t)
 {
-       if (hdr->entry_len < 15) {
+       if (len < 15) {
                t->drive_strength = boot->drive_strength;
                t->odt = boot->odt;
        } else {
 
        if (e->tCL >= NV_MEM_CL_GDDR5_MAX) {
                NV_WARN(dev, "(%u) Invalid tCL: %u", t->id, e->tCL);
-               return false;
+               return -ERANGE;
        }
 
        if (e->tWR >= NV_MEM_WR_GDDR5_MAX) {
                NV_WARN(dev, "(%u) Invalid tWR: %u", t->id, e->tWR);
-               return false;
+               return -ERANGE;
        }
 
        if (t->odt > 3) {
                   (t->odt << 2);
 
        NV_DEBUG(dev, "(%u) MR: %08x %08x", t->id, t->mr[0], t->mr[1]);
-       return true;
+       return 0;
 }
 
-static void
-nouveau_mem_copy_current_timings(struct drm_device *dev,
-                                struct nouveau_pm_memtiming *t)
+struct nouveau_pm_memtiming *
+nouveau_mem_timing(struct drm_device *dev, u32 freq)
+{
+       struct drm_nouveau_private *dev_priv = dev->dev_private;
+       struct nouveau_pm_engine *pm = &dev_priv->engine.pm;
+       struct nouveau_pm_memtiming *boot = &pm->boot_timing;
+       struct nouveau_pm_memtiming *t;
+       struct nouveau_pm_tbl_entry *e;
+       u8 ver, len, *ptr;
+       int ret;
+
+       ptr = nouveau_perf_timing(dev, freq, &ver, &len);
+       if (!ptr || ptr[0] == 0x00)
+               return boot;
+       e = (struct nouveau_pm_tbl_entry *)ptr;
+
+       t = kzalloc(sizeof(*t), GFP_KERNEL);
+       if (t) {
+               t->tCWL = boot->tCWL;
+
+               switch (dev_priv->card_type) {
+               case NV_40:
+                       ret = nv40_mem_timing_calc(dev, freq, e, len, boot, t);
+                       break;
+               case NV_50:
+                       ret = nv50_mem_timing_calc(dev, freq, e, len, boot, t);
+                       break;
+               case NV_C0:
+                       ret = nvc0_mem_timing_calc(dev, freq, e, len, boot, t);
+                       break;
+               default:
+                       ret = -ENODEV;
+                       break;
+               }
+
+               switch (dev_priv->vram_type * !ret) {
+               case NV_MEM_TYPE_GDDR3:
+                       ret = nouveau_mem_gddr3_mr(dev, freq, e, len, boot, t);
+                       break;
+               case NV_MEM_TYPE_GDDR5:
+                       ret = nouveau_mem_gddr5_mr(dev, freq, e, len, boot, t);
+                       break;
+               case NV_MEM_TYPE_DDR2:
+                       ret = nouveau_mem_ddr2_mr(dev, freq, e, len, boot, t);
+                       break;
+               case NV_MEM_TYPE_DDR3:
+                       ret = nouveau_mem_ddr3_mr(dev, freq, e, len, boot, t);
+                       break;
+               }
+
+               if (ret) {
+                       kfree(t);
+                       t = NULL;
+               }
+       }
+
+       return t;
+}
+
+void
+nouveau_mem_timing_read(struct drm_device *dev, struct nouveau_pm_memtiming *t)
 {
        struct drm_nouveau_private *dev_priv = dev->dev_private;
        u32 timing_base, timing_regs, mr_base;
        }
 }
 
-static bool
-nouveau_mem_compare_timings(struct drm_device *dev,
-                           struct nouveau_pm_memtiming *t1,
-                           struct nouveau_pm_memtiming *t2)
-{
-       struct drm_nouveau_private *dev_priv = dev->dev_private;
-
-       switch (dev_priv->card_type) {
-       case 0x50:
-               if (t1->reg[8] != t2->reg[8] ||
-                   t1->reg[7] != t2->reg[7] ||
-                   t1->reg[6] != t2->reg[6] ||
-                   t1->reg[5] != t2->reg[5])
-                       return false;
-       case 0xC0:
-               if (t1->reg[4] != t2->reg[4] ||
-                   t1->reg[3] != t2->reg[3])
-                       return false;
-       case 0x40:
-               if (t1->reg[2] != t2->reg[2] ||
-                   t1->reg[1] != t2->reg[1] ||
-                   t1->reg[0] != t2->reg[0])
-                       return false;
-               break;
-       default:
-               return false;
-       }
-
-       /* RSpliet: may generate many false negatives */
-       switch (dev_priv->vram_type) {
-       case NV_MEM_TYPE_GDDR3:
-       case NV_MEM_TYPE_GDDR5:
-               if (t1->mr[0] == t2->mr[0] ||
-                   t1->mr[1] != t2->mr[1])
-                       return true;
-               break;
-       case NV_MEM_TYPE_DDR3:
-               if (t1->mr[2] == t2->mr[2])
-                       return true;
-       case NV_MEM_TYPE_DDR2:
-               if (t1->mr[0] == t2->mr[0])
-                       return true;
-               break;
-       default:
-               return false;
-       }
-
-       return false;
-}
-
-/**
- * Processes the Memory Timing BIOS table, stores generated
- * register values
- * @pre init scripts were run, memtiming regs are initialized
- */
-void
-nouveau_mem_timing_init(struct drm_device *dev)
-{
-       struct drm_nouveau_private *dev_priv = dev->dev_private;
-       struct nouveau_pm_engine *pm = &dev_priv->engine.pm;
-       struct nouveau_pm_memtimings *memtimings = &pm->memtimings;
-       struct nvbios *bios = &dev_priv->vbios;
-       struct bit_entry P;
-       struct nouveau_pm_tbl_header *hdr = NULL;
-       bool valid_generation = false;
-       u8 *entry;
-       int i;
-
-       memtimings->nr_timing = 0;
-       memtimings->nr_timing_valid = 0;
-       memtimings->supported = 0;
-
-       if (dev_priv->card_type < NV_40) {
-               NV_ERROR(dev, "Timing entry format unknown for card_type %x. "
-                        "please contact nouveau developers",
-                        dev_priv->card_type);
-               return;
-       }
-
-       /* Copy the current timings */
-       nouveau_mem_copy_current_timings(dev, &memtimings->boot);
-
-       if (bios->type == NVBIOS_BIT) {
-               if (bit_table(dev, 'P', &P))
-                       return;
-
-               if (P.version == 1)
-                       hdr = (struct nouveau_pm_tbl_header *) ROMPTR(dev,
-                                                                    P.data[4]);
-               else if (P.version == 2)
-                       hdr = (struct nouveau_pm_tbl_header *) ROMPTR(dev,
-                                                                    P.data[8]);
-               else
-                       NV_WARN(dev, "unknown mem for BIT P %d\n", P.version);
-       } else {
-               NV_DEBUG(dev, "BMP version too old for memory\n");
-               return;
-       }
-
-       if (!hdr) {
-               NV_DEBUG(dev, "memory timing table pointer invalid\n");
-               return;
-       }
-
-       if (hdr->version != 0x10) {
-               NV_WARN(dev, "memory timing table 0x%02x unknown\n",
-                       hdr->version);
-               return;
-       }
-
-       /* validate record length */
-       if (hdr->entry_len < 15) {
-               NV_ERROR(dev, "mem timing table length unknown: %d\n",
-                        hdr->entry_len);
-               return;
-       }
-
-       /* parse vbios entries into common format */
-       memtimings->timing = kcalloc(hdr->entry_cnt,
-                                    sizeof(*memtimings->timing), GFP_KERNEL);
-       if (!memtimings->timing)
-               return;
-
-       entry = (u8 *) hdr + hdr->header_len;
-       for (i = 0; i < hdr->entry_cnt; i++, entry += hdr->entry_len) {
-               struct nouveau_pm_memtiming *timing = &pm->memtimings.timing[i];
-               struct nouveau_pm_tbl_entry *entry_struct =
-                                         (struct nouveau_pm_tbl_entry *) entry;
-               if (entry[0] == 0)
-                       continue;
-               memtimings->nr_timing_valid++;
-
-               timing->id = i;
-               timing->tCWL = memtimings->boot.tCWL;
-
-               /* generate the timngs */
-               if (dev_priv->card_type == NV_40) {
-                       nv40_mem_timing_entry(dev, hdr, entry_struct,
-                                             &pm->memtimings.timing[i],
-                                             &memtimings->boot);
-               } else if (dev_priv->card_type == NV_50) {
-                       nv50_mem_timing_entry(dev, &P, hdr, entry_struct,
-                                             &pm->memtimings.timing[i],
-                                             &memtimings->boot);
-               } else if (dev_priv->card_type == NV_C0) {
-                       nvc0_mem_timing_entry(dev, hdr, entry_struct,
-                                             &pm->memtimings.timing[i],
-                                             &memtimings->boot);
-               }
-
-               /* generate the MR/EMR/...  */
-               switch (dev_priv->vram_type) {
-               case NV_MEM_TYPE_GDDR3:
-                       nouveau_mem_gddr3_mr(dev, hdr, entry_struct, timing,
-                                            &memtimings->boot);
-                       break;
-               case NV_MEM_TYPE_GDDR5:
-                       nouveau_mem_gddr5_mr(dev, hdr, entry_struct, timing,
-                                            &memtimings->boot);
-                       break;
-               case NV_MEM_TYPE_DDR2:
-                       nouveau_mem_ddr2_mr(dev, hdr, entry_struct, timing,
-                                           &memtimings->boot);
-                       break;
-               case NV_MEM_TYPE_DDR3:
-                       nouveau_mem_ddr3_mr(dev, hdr, entry_struct, timing,
-                                           &memtimings->boot);
-                       break;
-               default:
-                       valid_generation = false;
-                       break;
-               }
-
-               /* some kind of validation */
-               if (nouveau_mem_compare_timings(dev, timing,
-                                               &memtimings->boot)) {
-                       NV_DEBUG(dev, "Copy boot timings from entry %d\n",
-                               timing->id);
-                       memtimings->boot = *timing;
-                       valid_generation = true;
-               }
-       }
-
-       memtimings->nr_timing = hdr->entry_cnt;
-       memtimings->supported = (P.version == 1) && valid_generation;
-
-       /* if there are no timing entries that cannot
-        * re-generate the current timings
-        */
-       if (memtimings->nr_timing_valid > 0  && !valid_generation) {
-               NV_INFO(dev,
-                        "Memory timings management may not be working."
-                        " please report to nouveau devs\n");
-       }
-}
-
-void
-nouveau_mem_timing_fini(struct drm_device *dev)
-{
-       struct drm_nouveau_private *dev_priv = dev->dev_private;
-       struct nouveau_pm_memtimings *mem = &dev_priv->engine.pm.memtimings;
-
-       kfree(mem->timing);
-       mem->timing = NULL;
-}
-
 int
 nouveau_mem_vbios_type(struct drm_device *dev)
 {