diff options
author | Ben Skeggs <bskeggs@redhat.com> | 2010-09-16 15:39:49 +1000 |
---|---|---|
committer | Ben Skeggs <bskeggs@redhat.com> | 2010-09-24 16:27:00 +1000 |
commit | 330c5988ee78045e6a731c3693251aaa5b0d14e3 (patch) | |
tree | f5313d35a321f665f2fa0e805a0317e1f97d3c28 | |
parent | 4709bff02adcb0d05d2d1a397e60581baa562de9 (diff) | |
download | blackbird-op-linux-330c5988ee78045e6a731c3693251aaa5b0d14e3.tar.gz blackbird-op-linux-330c5988ee78045e6a731c3693251aaa5b0d14e3.zip |
drm/nouveau: import initial work on vbios performance table parsing
Signed-off-by: Ben Skeggs <bskeggs@redhat.com>
-rw-r--r-- | drivers/gpu/drm/nouveau/Makefile | 1 | ||||
-rw-r--r-- | drivers/gpu/drm/nouveau/nouveau_drv.h | 46 | ||||
-rw-r--r-- | drivers/gpu/drm/nouveau/nouveau_perf.c | 159 | ||||
-rw-r--r-- | drivers/gpu/drm/nouveau/nouveau_pm.c | 214 | ||||
-rw-r--r-- | drivers/gpu/drm/nouveau/nouveau_pm.h | 44 | ||||
-rw-r--r-- | drivers/gpu/drm/nouveau/nouveau_state.c | 5 | ||||
-rw-r--r-- | drivers/gpu/drm/nouveau/nouveau_volt.c | 209 |
7 files changed, 678 insertions, 0 deletions
diff --git a/drivers/gpu/drm/nouveau/Makefile b/drivers/gpu/drm/nouveau/Makefile index d6cfbf259876..2fd61888a83d 100644 --- a/drivers/gpu/drm/nouveau/Makefile +++ b/drivers/gpu/drm/nouveau/Makefile @@ -10,6 +10,7 @@ nouveau-y := nouveau_drv.o nouveau_state.o nouveau_channel.o nouveau_mem.o \ nouveau_hw.o nouveau_calc.o nouveau_bios.o nouveau_i2c.o \ nouveau_display.o nouveau_connector.o nouveau_fbcon.o \ nouveau_dp.o nouveau_ramht.o \ + nouveau_pm.o nouveau_volt.o nouveau_perf.o \ nv04_timer.o \ nv04_mc.o nv40_mc.o nv50_mc.o \ nv04_fb.o nv10_fb.o nv30_fb.o nv40_fb.o nv50_fb.o nvc0_fb.o \ diff --git a/drivers/gpu/drm/nouveau/nouveau_drv.h b/drivers/gpu/drm/nouveau/nouveau_drv.h index d2fecc05eae4..bda4d1e7c63a 100644 --- a/drivers/gpu/drm/nouveau/nouveau_drv.h +++ b/drivers/gpu/drm/nouveau/nouveau_drv.h @@ -359,6 +359,51 @@ struct nouveau_gpio_engine { void (*irq_enable)(struct drm_device *, enum dcb_gpio_tag, bool on); }; +struct nouveau_pm_voltage_level { + u8 voltage; + u8 vid; +}; + +struct nouveau_pm_voltage { + bool supported; + u8 vid_mask; + + struct nouveau_pm_voltage_level *level; + int nr_level; +}; + +#define NOUVEAU_PM_MAX_LEVEL 8 +struct nouveau_pm_level { + struct device_attribute dev_attr; + char name[32]; + int id; + + u32 core; + u32 memory; + u32 shader; + u32 unk05; + + u8 voltage; + u8 fanspeed; +}; + +struct nouveau_pm_engine { + struct nouveau_pm_voltage voltage; + struct nouveau_pm_level perflvl[NOUVEAU_PM_MAX_LEVEL]; + int nr_perflvl; + + struct nouveau_pm_level boot; + struct nouveau_pm_level *cur; + + int (*clock_get)(struct drm_device *, u32 id); + void *(*clock_pre)(struct drm_device *, u32 id, int khz); + void (*clock_set)(struct drm_device *, void *); + int (*voltage_get)(struct drm_device *); + int (*voltage_set)(struct drm_device *, int voltage); + int (*fanspeed_get)(struct drm_device *); + int (*fanspeed_set)(struct drm_device *, int fanspeed); +}; + struct nouveau_engine { struct nouveau_instmem_engine instmem; struct nouveau_mc_engine mc; @@ -368,6 +413,7 @@ struct nouveau_engine { struct nouveau_fifo_engine fifo; struct nouveau_display_engine display; struct nouveau_gpio_engine gpio; + struct nouveau_pm_engine pm; }; struct nouveau_pll_vals { diff --git a/drivers/gpu/drm/nouveau/nouveau_perf.c b/drivers/gpu/drm/nouveau/nouveau_perf.c new file mode 100644 index 000000000000..a882a366487f --- /dev/null +++ b/drivers/gpu/drm/nouveau/nouveau_perf.c @@ -0,0 +1,159 @@ +/* + * Copyright 2010 Red Hat Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE COPYRIGHT HOLDER(S) OR AUTHOR(S) BE LIABLE FOR ANY CLAIM, DAMAGES OR + * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, + * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + * + * Authors: Ben Skeggs + */ + +#include "drmP.h" + +#include "nouveau_drv.h" +#include "nouveau_pm.h" + +void +nouveau_perf_init(struct drm_device *dev) +{ + struct drm_nouveau_private *dev_priv = dev->dev_private; + struct nouveau_pm_engine *pm = &dev_priv->engine.pm; + struct nvbios *bios = &dev_priv->vbios; + struct bit_entry P; + u8 version, headerlen, recordlen, entries; + u8 *perf, *entry; + int vid, i; + + if (bios->type == NVBIOS_BIT) { + if (bit_table(dev, 'P', &P)) + return; + + if (P.version != 1 && P.version != 2) { + NV_WARN(dev, "unknown perf for BIT P %d\n", P.version); + return; + } + + perf = ROMPTR(bios, P.data[0]); + version = perf[0]; + headerlen = perf[1]; + if (version < 0x40) { + recordlen = perf[3] + (perf[4] * perf[5]); + entries = perf[2]; + } else { + recordlen = perf[2] + (perf[3] * perf[4]); + entries = perf[5]; + } + } else { + if (bios->data[bios->offset + 6] < 0x27) { + NV_DEBUG(dev, "BMP version too old for perf\n"); + return; + } + + perf = ROMPTR(bios, bios->data[bios->offset + 0x94]); + if (!perf) { + NV_DEBUG(dev, "perf table pointer invalid\n"); + return; + } + + version = perf[1]; + headerlen = perf[0]; + recordlen = perf[3]; + entries = perf[2]; + } + + entry = perf + headerlen; + for (i = 0; i < entries; i++) { + struct nouveau_pm_level *perflvl = &pm->perflvl[pm->nr_perflvl]; + + if (entry[0] == 0xff) { + entry += recordlen; + continue; + } + + switch (version) { + case 0x12: + case 0x13: + case 0x15: + perflvl->fanspeed = entry[55]; + perflvl->voltage = entry[56]; + perflvl->core = ROM32(entry[1]) / 100; + perflvl->memory = ROM32(entry[5]) / 100; + break; + case 0x21: + case 0x23: + case 0x24: + perflvl->fanspeed = entry[4]; + perflvl->voltage = entry[5]; + perflvl->core = ROM16(entry[6]); + perflvl->memory = ROM16(entry[11]); + break; + case 0x25: + perflvl->fanspeed = entry[4]; + perflvl->voltage = entry[5]; + perflvl->core = ROM16(entry[6]); + perflvl->shader = ROM16(entry[10]); + perflvl->memory = ROM16(entry[12]); + break; + case 0x30: + case 0x35: + perflvl->fanspeed = entry[6]; + perflvl->voltage = entry[7]; + perflvl->core = ROM16(entry[8]); + perflvl->shader = ROM16(entry[10]); + perflvl->memory = ROM16(entry[12]); + /*XXX: confirm on 0x35 */ + perflvl->unk05 = ROM16(entry[16]); + break; + case 0x40: +#define subent(n) entry[perf[2] + ((n) * perf[3])] + perflvl->fanspeed = 0; /*XXX*/ + perflvl->voltage = 0; /*XXX: entry[2] */; + perflvl->core = ROM16(subent(0)) & 0xfff; + perflvl->shader = ROM16(subent(1)) & 0xfff; + perflvl->memory = ROM16(subent(2)) & 0xfff; + break; + } + + /* convert MHz -> KHz, it's more convenient */ + perflvl->core *= 1000; + perflvl->memory *= 1000; + perflvl->shader *= 1000; + perflvl->unk05 *= 1000; + + /* make sure vid is valid */ + if (pm->voltage.supported && perflvl->voltage) { + vid = nouveau_volt_vid_lookup(dev, perflvl->voltage); + if (vid < 0) { + NV_DEBUG(dev, "drop perflvl %d, bad vid\n", i); + entry += recordlen; + continue; + } + } + + snprintf(perflvl->name, sizeof(perflvl->name), + "performance_level_%d", i); + perflvl->id = i; + pm->nr_perflvl++; + + entry += recordlen; + } +} + +void +nouveau_perf_fini(struct drm_device *dev) +{ +} diff --git a/drivers/gpu/drm/nouveau/nouveau_pm.c b/drivers/gpu/drm/nouveau/nouveau_pm.c new file mode 100644 index 000000000000..9cf5fd665b8c --- /dev/null +++ b/drivers/gpu/drm/nouveau/nouveau_pm.c @@ -0,0 +1,214 @@ +/* + * Copyright 2010 Red Hat Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE COPYRIGHT HOLDER(S) OR AUTHOR(S) BE LIABLE FOR ANY CLAIM, DAMAGES OR + * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, + * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + * + * Authors: Ben Skeggs + */ + +#include "drmP.h" + +#include "nouveau_drv.h" +#include "nouveau_pm.h" + +static int +nouveau_pm_perflvl_get(struct drm_device *dev, struct nouveau_pm_level *perflvl) +{ + struct drm_nouveau_private *dev_priv = dev->dev_private; + struct nouveau_pm_engine *pm = &dev_priv->engine.pm; + int ret; + + if (!pm->clock_get) + return -EINVAL; + + memset(perflvl, 0, sizeof(*perflvl)); + + ret = pm->clock_get(dev, PLL_CORE); + if (ret > 0) + perflvl->core = ret; + + ret = pm->clock_get(dev, PLL_MEMORY); + if (ret > 0) + perflvl->memory = ret; + + ret = pm->clock_get(dev, PLL_SHADER); + if (ret > 0) + perflvl->shader = ret; + + ret = pm->clock_get(dev, PLL_UNK05); + if (ret > 0) + perflvl->unk05 = ret; + + if (pm->voltage.supported && pm->voltage_get) { + ret = pm->voltage_get(dev); + if (ret > 0) + perflvl->voltage = ret; + } + + return 0; +} + +static void +nouveau_pm_perflvl_info(struct nouveau_pm_level *perflvl, char *ptr, int len) +{ + char s[16], v[16], f[16]; + + s[0] = '\0'; + if (perflvl->shader) + snprintf(s, sizeof(s), " shader %dMHz", perflvl->shader / 1000); + + v[0] = '\0'; + if (perflvl->voltage) + snprintf(v, sizeof(v), " voltage %dmV", perflvl->voltage * 10); + + f[0] = '\0'; + if (perflvl->fanspeed) + snprintf(f, sizeof(f), " fanspeed %d%%", perflvl->fanspeed); + + snprintf(ptr, len, "core %dMHz memory %dMHz%s%s%s\n", + perflvl->core / 1000, perflvl->memory / 1000, s, v, f); +} + +static ssize_t +nouveau_pm_get_perflvl_info(struct device *d, + struct device_attribute *a, char *buf) +{ + struct nouveau_pm_level *perflvl = (struct nouveau_pm_level *)a; + char *ptr = buf; + int len = PAGE_SIZE; + + snprintf(ptr, len, "%d: ", perflvl->id); + ptr += strlen(buf); + len -= strlen(buf); + + nouveau_pm_perflvl_info(perflvl, ptr, len); + return strlen(buf); +} + +static ssize_t +nouveau_pm_get_perflvl(struct device *d, struct device_attribute *a, char *buf) +{ + struct drm_device *dev = pci_get_drvdata(to_pci_dev(d)); + struct drm_nouveau_private *dev_priv = dev->dev_private; + struct nouveau_pm_engine *pm = &dev_priv->engine.pm; + struct nouveau_pm_level cur; + int len = PAGE_SIZE, ret; + char *ptr = buf; + + if (!pm->cur) + snprintf(ptr, len, "setting: boot\n"); + else if (pm->cur == &pm->boot) + snprintf(ptr, len, "setting: boot\nc: "); + else + snprintf(ptr, len, "setting: static %d\nc: ", pm->cur->id); + ptr += strlen(buf); + len -= strlen(buf); + + ret = nouveau_pm_perflvl_get(dev, &cur); + if (ret == 0) + nouveau_pm_perflvl_info(&cur, ptr, len); + return strlen(buf); +} + +static ssize_t +nouveau_pm_set_perflvl(struct device *d, struct device_attribute *a, + const char *buf, size_t count) +{ + return -EPERM; +} + +DEVICE_ATTR(performance_level, S_IRUGO | S_IWUSR, + nouveau_pm_get_perflvl, nouveau_pm_set_perflvl); + +int +nouveau_pm_init(struct drm_device *dev) +{ + struct drm_nouveau_private *dev_priv = dev->dev_private; + struct nouveau_pm_engine *pm = &dev_priv->engine.pm; + struct device *d = &dev->pdev->dev; + char info[256]; + int ret, i; + + nouveau_volt_init(dev); + nouveau_perf_init(dev); + + NV_INFO(dev, "%d available performance level(s)\n", pm->nr_perflvl); + for (i = 0; i < pm->nr_perflvl; i++) { + nouveau_pm_perflvl_info(&pm->perflvl[i], info, sizeof(info)); + NV_INFO(dev, "%d: %s", pm->perflvl[i].id, info); + } + + /* determine current ("boot") performance level */ + ret = nouveau_pm_perflvl_get(dev, &pm->boot); + if (ret == 0) { + pm->cur = &pm->boot; + + nouveau_pm_perflvl_info(&pm->boot, info, sizeof(info)); + NV_INFO(dev, "c: %s", info); + } + + /* initialise sysfs */ + ret = device_create_file(d, &dev_attr_performance_level); + if (ret) + return ret; + + for (i = 0; i < pm->nr_perflvl; i++) { + struct nouveau_pm_level *perflvl = &pm->perflvl[i]; + + perflvl->dev_attr.attr.name = perflvl->name; + perflvl->dev_attr.attr.mode = S_IRUGO; + perflvl->dev_attr.show = nouveau_pm_get_perflvl_info; + perflvl->dev_attr.store = NULL; + sysfs_attr_init(&perflvl->dev_attr.attr); + + ret = device_create_file(d, &perflvl->dev_attr); + if (ret) { + NV_ERROR(dev, "failed pervlvl %d sysfs: %d\n", + perflvl->id, i); + perflvl->dev_attr.attr.name = NULL; + nouveau_pm_fini(dev); + return ret; + } + } + + return 0; +} + +void +nouveau_pm_fini(struct drm_device *dev) +{ + struct drm_nouveau_private *dev_priv = dev->dev_private; + struct nouveau_pm_engine *pm = &dev_priv->engine.pm; + struct device *d = &dev->pdev->dev; + int i; + + device_remove_file(d, &dev_attr_performance_level); + for (i = 0; i < pm->nr_perflvl; i++) { + struct nouveau_pm_level *pl = &pm->perflvl[i]; + + if (!pl->dev_attr.attr.name) + break; + + device_remove_file(d, &pl->dev_attr); + } + + nouveau_perf_fini(dev); + nouveau_volt_fini(dev); +} + diff --git a/drivers/gpu/drm/nouveau/nouveau_pm.h b/drivers/gpu/drm/nouveau/nouveau_pm.h new file mode 100644 index 000000000000..a401ec0a1269 --- /dev/null +++ b/drivers/gpu/drm/nouveau/nouveau_pm.h @@ -0,0 +1,44 @@ +/* + * Copyright 2010 Red Hat Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE COPYRIGHT HOLDER(S) OR AUTHOR(S) BE LIABLE FOR ANY CLAIM, DAMAGES OR + * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, + * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + * + * Authors: Ben Skeggs + */ + +#ifndef __NOUVEAU_PM_H__ +#define __NOUVEAU_PM_H__ + +/* nouveau_pm.c */ +int nouveau_pm_init(struct drm_device *dev); +void nouveau_pm_fini(struct drm_device *dev); + +/* nouveau_volt.c */ +void nouveau_volt_init(struct drm_device *); +void nouveau_volt_fini(struct drm_device *); +int nouveau_volt_vid_lookup(struct drm_device *, int voltage); +int nouveau_volt_lvl_lookup(struct drm_device *, int vid); +int nouveau_voltage_gpio_get(struct drm_device *); +int nouveau_voltage_gpio_set(struct drm_device *, int voltage); + +/* nouveau_perf.c */ +void nouveau_perf_init(struct drm_device *); +void nouveau_perf_fini(struct drm_device *); + +#endif diff --git a/drivers/gpu/drm/nouveau/nouveau_state.c b/drivers/gpu/drm/nouveau/nouveau_state.c index be859604cf64..18c4a8a85940 100644 --- a/drivers/gpu/drm/nouveau/nouveau_state.c +++ b/drivers/gpu/drm/nouveau/nouveau_state.c @@ -36,6 +36,7 @@ #include "nouveau_drm.h" #include "nouveau_fbcon.h" #include "nouveau_ramht.h" +#include "nouveau_pm.h" #include "nv50_display.h" static void nouveau_stub_takedown(struct drm_device *dev) {} @@ -527,6 +528,8 @@ nouveau_card_init(struct drm_device *dev) if (ret) goto out_display_early; + nouveau_pm_init(dev); + ret = nouveau_mem_vram_init(dev); if (ret) goto out_bios; @@ -635,6 +638,7 @@ out_gpuobj: out_vram: nouveau_mem_vram_fini(dev); out_bios: + nouveau_pm_fini(dev); nouveau_bios_takedown(dev); out_display_early: engine->display.late_takedown(dev); @@ -677,6 +681,7 @@ static void nouveau_card_takedown(struct drm_device *dev) drm_irq_uninstall(dev); + nouveau_pm_fini(dev); nouveau_bios_takedown(dev); vga_client_register(dev->pdev, NULL, NULL, NULL); diff --git a/drivers/gpu/drm/nouveau/nouveau_volt.c b/drivers/gpu/drm/nouveau/nouveau_volt.c new file mode 100644 index 000000000000..6ce857688eb6 --- /dev/null +++ b/drivers/gpu/drm/nouveau/nouveau_volt.c @@ -0,0 +1,209 @@ +/* + * Copyright 2010 Red Hat Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE COPYRIGHT HOLDER(S) OR AUTHOR(S) BE LIABLE FOR ANY CLAIM, DAMAGES OR + * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, + * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + * + * Authors: Ben Skeggs + */ + +#include "drmP.h" + +#include "nouveau_drv.h" +#include "nouveau_pm.h" + +static const enum dcb_gpio_tag vidtag[] = { 0x04, 0x05, 0x06, 0x1a }; +static int nr_vidtag = sizeof(vidtag) / sizeof(vidtag[0]); + +int +nouveau_voltage_gpio_get(struct drm_device *dev) +{ + struct drm_nouveau_private *dev_priv = dev->dev_private; + struct nouveau_gpio_engine *gpio = &dev_priv->engine.gpio; + struct nouveau_pm_voltage *volt = &dev_priv->engine.pm.voltage; + u8 vid = 0; + int i; + + for (i = 0; i < nr_vidtag; i++) { + if (!(volt->vid_mask & (1 << i))) + continue; + + vid |= gpio->get(dev, vidtag[i]) << i; + } + + return nouveau_volt_lvl_lookup(dev, vid); +} + +int +nouveau_voltage_gpio_set(struct drm_device *dev, int voltage) +{ + struct drm_nouveau_private *dev_priv = dev->dev_private; + struct nouveau_gpio_engine *gpio = &dev_priv->engine.gpio; + struct nouveau_pm_voltage *volt = &dev_priv->engine.pm.voltage; + int vid, i; + + vid = nouveau_volt_vid_lookup(dev, voltage); + if (vid < 0) + return vid; + + for (i = 0; i < nr_vidtag; i++) { + if (!(volt->vid_mask & (1 << i))) + continue; + + gpio->set(dev, vidtag[i], !!(vid & (1 << i))); + } + + return 0; +} + +int +nouveau_volt_vid_lookup(struct drm_device *dev, int voltage) +{ + struct drm_nouveau_private *dev_priv = dev->dev_private; + struct nouveau_pm_voltage *volt = &dev_priv->engine.pm.voltage; + int i; + + for (i = 0; i < volt->nr_level; i++) { + if (volt->level[i].voltage == voltage) + return volt->level[i].vid; + } + + return -ENOENT; +} + +int +nouveau_volt_lvl_lookup(struct drm_device *dev, int vid) +{ + struct drm_nouveau_private *dev_priv = dev->dev_private; + struct nouveau_pm_voltage *volt = &dev_priv->engine.pm.voltage; + int i; + + for (i = 0; i < volt->nr_level; i++) { + if (volt->level[i].vid == vid) + return volt->level[i].voltage; + } + + return -ENOENT; +} + +void +nouveau_volt_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_voltage *voltage = &pm->voltage; + struct nvbios *bios = &dev_priv->vbios; + struct bit_entry P; + u8 *volt = NULL, *entry; + int i, recordlen, entries, vidmask, vidshift; + + if (bios->type == NVBIOS_BIT) { + if (bit_table(dev, 'P', &P)) + return; + + if (P.version == 1) + volt = ROMPTR(bios, P.data[16]); + else + if (P.version == 2) + volt = ROMPTR(bios, P.data[12]); + else { + NV_WARN(dev, "unknown volt for BIT P %d\n", P.version); + } + } else { + if (bios->data[bios->offset + 6] < 0x27) { + NV_DEBUG(dev, "BMP version too old for voltage\n"); + return; + } + + volt = ROMPTR(bios, bios->data[bios->offset + 0x98]); + } + + if (!volt) { + NV_DEBUG(dev, "voltage table pointer invalid\n"); + return; + } + + switch (volt[0]) { + case 0x10: + case 0x11: + case 0x12: + recordlen = 5; + entries = volt[2]; + vidshift = 0; + vidmask = volt[4]; + break; + case 0x20: + recordlen = volt[3]; + entries = volt[2]; + vidshift = 0; /* could be vidshift like 0x30? */ + vidmask = volt[5]; + break; + case 0x30: + recordlen = volt[2]; + entries = volt[3]; + vidshift = hweight8(volt[5]); + vidmask = volt[4]; + break; + default: + NV_WARN(dev, "voltage table 0x%02x unknown\n", volt[0]); + return; + } + + /* validate vid mask */ + voltage->vid_mask = vidmask; + if (!voltage->vid_mask) + return; + + i = 0; + while (vidmask) { + if (i > nr_vidtag) { + NV_DEBUG(dev, "vid bit %d unknown\n", i); + return; + } + + if (!nouveau_bios_gpio_entry(dev, vidtag[i])) { + NV_DEBUG(dev, "vid bit %d has no gpio tag\n", i); + return; + } + + vidmask >>= 1; + i++; + } + + /* parse vbios entries into common format */ + voltage->level = kcalloc(entries, sizeof(*voltage->level), GFP_KERNEL); + if (!voltage->level) + return; + + entry = volt + volt[1]; + for (i = 0; i < entries; i++, entry += recordlen) { + voltage->level[i].voltage = entry[0]; + voltage->level[i].vid = entry[1] >> vidshift; + } + voltage->nr_level = entries; + voltage->supported = true; +} + +void +nouveau_volt_fini(struct drm_device *dev) +{ + struct drm_nouveau_private *dev_priv = dev->dev_private; + struct nouveau_pm_voltage *volt = &dev_priv->engine.pm.voltage; + + kfree(volt->level); +} |