summaryrefslogtreecommitdiffstats
path: root/drivers/gpu/drm/nouveau/core/subdev/device/base.c
diff options
context:
space:
mode:
authorDave Airlie <airlied@redhat.com>2012-10-03 13:26:15 +1000
committerDave Airlie <airlied@redhat.com>2012-10-03 13:26:15 +1000
commit268d28371cd326be4dfcd7eba5917bf4b9d30c8f (patch)
treefec4f9e98bde15301b5d5338038a9a31f7555456 /drivers/gpu/drm/nouveau/core/subdev/device/base.c
parentdf86b5765a48d5f557489577652bd6df145b0e1b (diff)
parentb9f10852fcb1f09369d931dcbfbaad89ad1da4ad (diff)
downloadop-kernel-dev-268d28371cd326be4dfcd7eba5917bf4b9d30c8f.zip
op-kernel-dev-268d28371cd326be4dfcd7eba5917bf4b9d30c8f.tar.gz
Merge branch 'drm-nouveau-next' of git://anongit.freedesktop.org/git/nouveau/linux-2.6 into drm-next
This is a major rework of the nouveau driver core, to reflect more closely how the hw is used and to make it easier to implement newer features now that the GPUs are more clearly understood than when nouveau started. It also contains a few other bits: thermal patches nv41/44 pcie gart fixes i2c unregistering fixes. * 'drm-nouveau-next' of git://anongit.freedesktop.org/git/nouveau/linux-2.6: (191 commits) drm/nv98/crypt: fix fuc build with latest envyas drm/nouveau/devinit: fixup various issues with subdev ctor/init ordering drm/nv41/vm: fix and enable use of "real" pciegart drm/nv44/vm: fix and enable use of "real" pciegart drm/nv04/dmaobj: fixup vm target handling in preparation for nv4x pcie drm/nouveau: store supported dma mask in vmmgr drm/nvc0/ibus: initial implementation of subdev drm/nouveau/therm: add support for fan-control modes drm/nouveau/hwmon: rename pwm0* to pmw1* to follow hwmon's rules drm/nouveau/therm: calculate the pwm divisor on nv50+ drm/nouveau/fan: rewrite the fan tachometer driver to get more precision, faster drm/nouveau/therm: move thermal-related functions to the therm subdev drm/nouveau/bios: parse the pwm divisor from the perf table drm/nouveau/therm: use the EXTDEV table to detect i2c monitoring devices drm/nouveau/therm: rework thermal table parsing drm/nouveau/gpio: expose the PWM/TOGGLE parameter found in the gpio vbios table drm/nouveau: fix pm initialization order drm/nouveau/bios: check that fixed tvdac gpio data is valid before using it drm/nouveau: log channel debug/error messages from client object rather than drm client drm/nouveau: have drm debugging macros build on top of core macros ... Conflicts: drivers/gpu/drm/nouveau/nouveau_dp.c
Diffstat (limited to 'drivers/gpu/drm/nouveau/core/subdev/device/base.c')
-rw-r--r--drivers/gpu/drm/nouveau/core/subdev/device/base.c472
1 files changed, 472 insertions, 0 deletions
diff --git a/drivers/gpu/drm/nouveau/core/subdev/device/base.c b/drivers/gpu/drm/nouveau/core/subdev/device/base.c
new file mode 100644
index 0000000..ca9a464
--- /dev/null
+++ b/drivers/gpu/drm/nouveau/core/subdev/device/base.c
@@ -0,0 +1,472 @@
+/*
+ * Copyright 2012 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 <core/object.h>
+#include <core/device.h>
+#include <core/client.h>
+#include <core/device.h>
+#include <core/option.h>
+
+#include <core/class.h>
+
+#include <subdev/device.h>
+
+static DEFINE_MUTEX(nv_devices_mutex);
+static LIST_HEAD(nv_devices);
+
+struct nouveau_device *
+nouveau_device_find(u64 name)
+{
+ struct nouveau_device *device, *match = NULL;
+ mutex_lock(&nv_devices_mutex);
+ list_for_each_entry(device, &nv_devices, head) {
+ if (device->handle == name) {
+ match = device;
+ break;
+ }
+ }
+ mutex_unlock(&nv_devices_mutex);
+ return match;
+}
+
+/******************************************************************************
+ * nouveau_devobj (0x0080): class implementation
+ *****************************************************************************/
+struct nouveau_devobj {
+ struct nouveau_parent base;
+ struct nouveau_object *subdev[NVDEV_SUBDEV_NR];
+ bool created;
+};
+
+static const u64 disable_map[] = {
+ [NVDEV_SUBDEV_VBIOS] = NV_DEVICE_DISABLE_VBIOS,
+ [NVDEV_SUBDEV_GPIO] = NV_DEVICE_DISABLE_CORE,
+ [NVDEV_SUBDEV_I2C] = NV_DEVICE_DISABLE_CORE,
+ [NVDEV_SUBDEV_DEVINIT] = NV_DEVICE_DISABLE_CORE,
+ [NVDEV_SUBDEV_MC] = NV_DEVICE_DISABLE_CORE,
+ [NVDEV_SUBDEV_TIMER] = NV_DEVICE_DISABLE_CORE,
+ [NVDEV_SUBDEV_FB] = NV_DEVICE_DISABLE_CORE,
+ [NVDEV_SUBDEV_VM] = NV_DEVICE_DISABLE_CORE,
+ [NVDEV_SUBDEV_INSTMEM] = NV_DEVICE_DISABLE_CORE,
+ [NVDEV_SUBDEV_BAR] = NV_DEVICE_DISABLE_CORE,
+ [NVDEV_SUBDEV_VOLT] = NV_DEVICE_DISABLE_CORE,
+ [NVDEV_SUBDEV_CLOCK] = NV_DEVICE_DISABLE_CORE,
+ [NVDEV_SUBDEV_THERM] = NV_DEVICE_DISABLE_CORE,
+ [NVDEV_ENGINE_DMAOBJ] = NV_DEVICE_DISABLE_CORE,
+ [NVDEV_ENGINE_GR] = NV_DEVICE_DISABLE_GRAPH,
+ [NVDEV_ENGINE_MPEG] = NV_DEVICE_DISABLE_MPEG,
+ [NVDEV_ENGINE_ME] = NV_DEVICE_DISABLE_ME,
+ [NVDEV_ENGINE_VP] = NV_DEVICE_DISABLE_VP,
+ [NVDEV_ENGINE_CRYPT] = NV_DEVICE_DISABLE_CRYPT,
+ [NVDEV_ENGINE_BSP] = NV_DEVICE_DISABLE_BSP,
+ [NVDEV_ENGINE_PPP] = NV_DEVICE_DISABLE_PPP,
+ [NVDEV_ENGINE_COPY0] = NV_DEVICE_DISABLE_COPY0,
+ [NVDEV_ENGINE_COPY1] = NV_DEVICE_DISABLE_COPY1,
+ [NVDEV_ENGINE_UNK1C1] = NV_DEVICE_DISABLE_UNK1C1,
+ [NVDEV_ENGINE_FIFO] = NV_DEVICE_DISABLE_FIFO,
+ [NVDEV_ENGINE_DISP] = NV_DEVICE_DISABLE_DISP,
+ [NVDEV_SUBDEV_NR] = 0,
+};
+
+static int
+nouveau_devobj_ctor(struct nouveau_object *parent,
+ struct nouveau_object *engine,
+ struct nouveau_oclass *oclass, void *data, u32 size,
+ struct nouveau_object **pobject)
+{
+ struct nouveau_client *client = nv_client(parent);
+ struct nouveau_device *device;
+ struct nouveau_devobj *devobj;
+ struct nv_device_class *args = data;
+ u64 disable, boot0, strap;
+ u64 mmio_base, mmio_size;
+ void __iomem *map;
+ int ret, i, c;
+
+ if (size < sizeof(struct nv_device_class))
+ return -EINVAL;
+
+ /* find the device subdev that matches what the client requested */
+ device = nv_device(client->device);
+ if (args->device != ~0) {
+ device = nouveau_device_find(args->device);
+ if (!device)
+ return -ENODEV;
+ }
+
+ ret = nouveau_parent_create(parent, nv_object(device), oclass, 0, NULL,
+ (1ULL << NVDEV_ENGINE_DMAOBJ) |
+ (1ULL << NVDEV_ENGINE_FIFO) |
+ (1ULL << NVDEV_ENGINE_DISP), &devobj);
+ *pobject = nv_object(devobj);
+ if (ret)
+ return ret;
+
+ mmio_base = pci_resource_start(device->pdev, 0);
+ mmio_size = pci_resource_len(device->pdev, 0);
+
+ /* translate api disable mask into internal mapping */
+ disable = args->debug0;
+ for (i = 0; i < NVDEV_SUBDEV_NR; i++) {
+ if (args->disable & disable_map[i])
+ disable |= (1ULL << i);
+ }
+
+ /* identify the chipset, and determine classes of subdev/engines */
+ if (!(args->disable & NV_DEVICE_DISABLE_IDENTIFY) &&
+ !device->card_type) {
+ map = ioremap(mmio_base, 0x102000);
+ if (map == NULL)
+ return -ENOMEM;
+
+ /* switch mmio to cpu's native endianness */
+#ifndef __BIG_ENDIAN
+ if (ioread32_native(map + 0x000004) != 0x00000000)
+#else
+ if (ioread32_native(map + 0x000004) == 0x00000000)
+#endif
+ iowrite32_native(0x01000001, map + 0x000004);
+
+ /* read boot0 and strapping information */
+ boot0 = ioread32_native(map + 0x000000);
+ strap = ioread32_native(map + 0x101000);
+ iounmap(map);
+
+ /* determine chipset and derive architecture from it */
+ if ((boot0 & 0x0f000000) > 0) {
+ device->chipset = (boot0 & 0xff00000) >> 20;
+ switch (device->chipset & 0xf0) {
+ case 0x10: device->card_type = NV_10; break;
+ case 0x20: device->card_type = NV_20; break;
+ case 0x30: device->card_type = NV_30; break;
+ case 0x40:
+ case 0x60: device->card_type = NV_40; break;
+ case 0x50:
+ case 0x80:
+ case 0x90:
+ case 0xa0: device->card_type = NV_50; break;
+ case 0xc0: device->card_type = NV_C0; break;
+ case 0xd0: device->card_type = NV_D0; break;
+ case 0xe0: device->card_type = NV_E0; break;
+ default:
+ break;
+ }
+ } else
+ if ((boot0 & 0xff00fff0) == 0x20004000) {
+ if (boot0 & 0x00f00000)
+ device->chipset = 0x05;
+ else
+ device->chipset = 0x04;
+ device->card_type = NV_04;
+ }
+
+ switch (device->card_type) {
+ case NV_04: ret = nv04_identify(device); break;
+ case NV_10: ret = nv10_identify(device); break;
+ case NV_20: ret = nv20_identify(device); break;
+ case NV_30: ret = nv30_identify(device); break;
+ case NV_40: ret = nv40_identify(device); break;
+ case NV_50: ret = nv50_identify(device); break;
+ case NV_C0:
+ case NV_D0: ret = nvc0_identify(device); break;
+ case NV_E0: ret = nve0_identify(device); break;
+ default:
+ ret = -EINVAL;
+ break;
+ }
+
+ if (ret) {
+ nv_error(device, "unknown chipset, 0x%08x\n", boot0);
+ return ret;
+ }
+
+ nv_info(device, "BOOT0 : 0x%08x\n", boot0);
+ nv_info(device, "Chipset: %s (NV%02X)\n",
+ device->cname, device->chipset);
+ nv_info(device, "Family : NV%02X\n", device->card_type);
+
+ /* determine frequency of timing crystal */
+ if ( device->chipset < 0x17 ||
+ (device->chipset >= 0x20 && device->chipset <= 0x25))
+ strap &= 0x00000040;
+ else
+ strap &= 0x00400040;
+
+ switch (strap) {
+ case 0x00000000: device->crystal = 13500; break;
+ case 0x00000040: device->crystal = 14318; break;
+ case 0x00400000: device->crystal = 27000; break;
+ case 0x00400040: device->crystal = 25000; break;
+ }
+
+ nv_debug(device, "crystal freq: %dKHz\n", device->crystal);
+ }
+
+ if (!(args->disable & NV_DEVICE_DISABLE_MMIO) &&
+ !nv_subdev(device)->mmio) {
+ nv_subdev(device)->mmio = ioremap(mmio_base, mmio_size);
+ if (!nv_subdev(device)->mmio) {
+ nv_error(device, "unable to map device registers\n");
+ return -ENOMEM;
+ }
+ }
+
+ /* ensure requested subsystems are available for use */
+ for (i = 0, c = 0; i < NVDEV_SUBDEV_NR; i++) {
+ if (!(oclass = device->oclass[i]) || (disable & (1ULL << i)))
+ continue;
+
+ if (!device->subdev[i]) {
+ ret = nouveau_object_ctor(nv_object(device), NULL,
+ oclass, NULL, i,
+ &devobj->subdev[i]);
+ if (ret == -ENODEV)
+ continue;
+ if (ret)
+ return ret;
+
+ if (nv_iclass(devobj->subdev[i], NV_ENGINE_CLASS))
+ nouveau_subdev_reset(devobj->subdev[i]);
+ } else {
+ nouveau_object_ref(device->subdev[i],
+ &devobj->subdev[i]);
+ }
+
+ /* note: can't init *any* subdevs until devinit has been run
+ * due to not knowing exactly what the vbios init tables will
+ * mess with. devinit also can't be run until all of its
+ * dependencies have been created.
+ *
+ * this code delays init of any subdev until all of devinit's
+ * dependencies have been created, and then initialises each
+ * subdev in turn as they're created.
+ */
+ while (i >= NVDEV_SUBDEV_DEVINIT_LAST && c <= i) {
+ struct nouveau_object *subdev = devobj->subdev[c++];
+ if (subdev && !nv_iclass(subdev, NV_ENGINE_CLASS)) {
+ ret = nouveau_object_inc(subdev);
+ if (ret)
+ return ret;
+ }
+ }
+ }
+
+ return 0;
+}
+
+static void
+nouveau_devobj_dtor(struct nouveau_object *object)
+{
+ struct nouveau_devobj *devobj = (void *)object;
+ int i;
+
+ for (i = NVDEV_SUBDEV_NR - 1; i >= 0; i--)
+ nouveau_object_ref(NULL, &devobj->subdev[i]);
+
+ nouveau_parent_destroy(&devobj->base);
+}
+
+static int
+nouveau_devobj_init(struct nouveau_object *object)
+{
+ struct nouveau_devobj *devobj = (void *)object;
+ struct nouveau_object *subdev;
+ int ret, i;
+
+ ret = nouveau_parent_init(&devobj->base);
+ if (ret)
+ return ret;
+
+ for (i = 0; devobj->created && i < NVDEV_SUBDEV_NR; i++) {
+ if ((subdev = devobj->subdev[i])) {
+ if (!nv_iclass(subdev, NV_ENGINE_CLASS)) {
+ ret = nouveau_object_inc(subdev);
+ if (ret)
+ goto fail;
+ }
+ }
+ }
+
+ devobj->created = true;
+ return 0;
+
+fail:
+ for (--i; i >= 0; i--) {
+ if ((subdev = devobj->subdev[i])) {
+ if (!nv_iclass(subdev, NV_ENGINE_CLASS))
+ nouveau_object_dec(subdev, false);
+ }
+ }
+
+ return ret;
+}
+
+static int
+nouveau_devobj_fini(struct nouveau_object *object, bool suspend)
+{
+ struct nouveau_devobj *devobj = (void *)object;
+ struct nouveau_object *subdev;
+ int ret, i;
+
+ for (i = NVDEV_SUBDEV_NR - 1; i >= 0; i--) {
+ if ((subdev = devobj->subdev[i])) {
+ if (!nv_iclass(subdev, NV_ENGINE_CLASS)) {
+ ret = nouveau_object_dec(subdev, suspend);
+ if (ret && suspend)
+ goto fail;
+ }
+ }
+ }
+
+ ret = nouveau_parent_fini(&devobj->base, suspend);
+fail:
+ for (; ret && suspend && i < NVDEV_SUBDEV_NR; i++) {
+ if ((subdev = devobj->subdev[i])) {
+ if (!nv_iclass(subdev, NV_ENGINE_CLASS)) {
+ ret = nouveau_object_inc(subdev);
+ if (ret) {
+ /* XXX */
+ }
+ }
+ }
+ }
+
+ return ret;
+}
+
+static u8
+nouveau_devobj_rd08(struct nouveau_object *object, u32 addr)
+{
+ return nv_rd08(object->engine, addr);
+}
+
+static u16
+nouveau_devobj_rd16(struct nouveau_object *object, u32 addr)
+{
+ return nv_rd16(object->engine, addr);
+}
+
+static u32
+nouveau_devobj_rd32(struct nouveau_object *object, u32 addr)
+{
+ return nv_rd32(object->engine, addr);
+}
+
+static void
+nouveau_devobj_wr08(struct nouveau_object *object, u32 addr, u8 data)
+{
+ nv_wr08(object->engine, addr, data);
+}
+
+static void
+nouveau_devobj_wr16(struct nouveau_object *object, u32 addr, u16 data)
+{
+ nv_wr16(object->engine, addr, data);
+}
+
+static void
+nouveau_devobj_wr32(struct nouveau_object *object, u32 addr, u32 data)
+{
+ nv_wr32(object->engine, addr, data);
+}
+
+static struct nouveau_ofuncs
+nouveau_devobj_ofuncs = {
+ .ctor = nouveau_devobj_ctor,
+ .dtor = nouveau_devobj_dtor,
+ .init = nouveau_devobj_init,
+ .fini = nouveau_devobj_fini,
+ .rd08 = nouveau_devobj_rd08,
+ .rd16 = nouveau_devobj_rd16,
+ .rd32 = nouveau_devobj_rd32,
+ .wr08 = nouveau_devobj_wr08,
+ .wr16 = nouveau_devobj_wr16,
+ .wr32 = nouveau_devobj_wr32,
+};
+
+/******************************************************************************
+ * nouveau_device: engine functions
+ *****************************************************************************/
+struct nouveau_oclass
+nouveau_device_sclass[] = {
+ { 0x0080, &nouveau_devobj_ofuncs },
+ {}
+};
+
+static void
+nouveau_device_dtor(struct nouveau_object *object)
+{
+ struct nouveau_device *device = (void *)object;
+
+ mutex_lock(&nv_devices_mutex);
+ list_del(&device->head);
+ mutex_unlock(&nv_devices_mutex);
+
+ if (device->base.mmio)
+ iounmap(device->base.mmio);
+
+ nouveau_subdev_destroy(&device->base);
+}
+
+static struct nouveau_oclass
+nouveau_device_oclass = {
+ .handle = NV_SUBDEV(DEVICE, 0x00),
+ .ofuncs = &(struct nouveau_ofuncs) {
+ .dtor = nouveau_device_dtor,
+ },
+};
+
+int
+nouveau_device_create_(struct pci_dev *pdev, u64 name, const char *sname,
+ const char *cfg, const char *dbg,
+ int length, void **pobject)
+{
+ struct nouveau_device *device;
+ int ret = -EEXIST;
+
+ mutex_lock(&nv_devices_mutex);
+ list_for_each_entry(device, &nv_devices, head) {
+ if (device->handle == name)
+ goto done;
+ }
+
+ ret = nouveau_subdev_create_(NULL, NULL, &nouveau_device_oclass, 0,
+ "DEVICE", "device", length, pobject);
+ device = *pobject;
+ if (ret)
+ goto done;
+
+ atomic_set(&nv_object(device)->usecount, 2);
+ device->pdev = pdev;
+ device->handle = name;
+ device->cfgopt = cfg;
+ device->dbgopt = dbg;
+ device->name = sname;
+
+ nv_subdev(device)->debug = nouveau_dbgopt(device->dbgopt, "DEVICE");
+ list_add(&device->head, &nv_devices);
+done:
+ mutex_unlock(&nv_devices_mutex);
+ return ret;
+}
OpenPOWER on IntegriCloud