/* * 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 "priv.h" #include "acpi.h" #include #include #include #include #include #include #include #include #include static DEFINE_MUTEX(nv_devices_mutex); static LIST_HEAD(nv_devices); struct nvkm_device * nvkm_device_find(u64 name) { struct nvkm_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; } int nvkm_device_list(u64 *name, int size) { struct nvkm_device *device; int nr = 0; mutex_lock(&nv_devices_mutex); list_for_each_entry(device, &nv_devices, head) { if (nr++ < size) name[nr - 1] = device->handle; } mutex_unlock(&nv_devices_mutex); return nr; } /****************************************************************************** * nvkm_devobj (0x0080): class implementation *****************************************************************************/ struct nvkm_devobj { struct nvkm_parent base; struct nvkm_object *subdev[NVDEV_SUBDEV_NR]; }; static int nvkm_devobj_info(struct nvkm_object *object, void *data, u32 size) { struct nvkm_device *device = nv_device(object); struct nvkm_fb *pfb = nvkm_fb(device); struct nvkm_instmem *imem = nvkm_instmem(device); union { struct nv_device_info_v0 v0; } *args = data; int ret; nv_ioctl(object, "device info size %d\n", size); if (nvif_unpack(args->v0, 0, 0, false)) { nv_ioctl(object, "device info vers %d\n", args->v0.version); } else return ret; switch (device->chipset) { case 0x01a: case 0x01f: case 0x04c: case 0x04e: case 0x063: case 0x067: case 0x068: case 0x0aa: case 0x0ac: case 0x0af: args->v0.platform = NV_DEVICE_INFO_V0_IGP; break; default: if (device->pdev) { if (pci_find_capability(device->pdev, PCI_CAP_ID_AGP)) args->v0.platform = NV_DEVICE_INFO_V0_AGP; else if (pci_is_pcie(device->pdev)) args->v0.platform = NV_DEVICE_INFO_V0_PCIE; else args->v0.platform = NV_DEVICE_INFO_V0_PCI; } else { args->v0.platform = NV_DEVICE_INFO_V0_SOC; } break; } switch (device->card_type) { case NV_04: args->v0.family = NV_DEVICE_INFO_V0_TNT; break; case NV_10: case NV_11: args->v0.family = NV_DEVICE_INFO_V0_CELSIUS; break; case NV_20: args->v0.family = NV_DEVICE_INFO_V0_KELVIN; break; case NV_30: args->v0.family = NV_DEVICE_INFO_V0_RANKINE; break; case NV_40: args->v0.family = NV_DEVICE_INFO_V0_CURIE; break; case NV_50: args->v0.family = NV_DEVICE_INFO_V0_TESLA; break; case NV_C0: args->v0.family = NV_DEVICE_INFO_V0_FERMI; break; case NV_E0: args->v0.family = NV_DEVICE_INFO_V0_KEPLER; break; case GM100: args->v0.family = NV_DEVICE_INFO_V0_MAXWELL; break; default: args->v0.family = 0; break; } args->v0.chipset = device->chipset; args->v0.revision = device->chiprev; if (pfb && pfb->ram) args->v0.ram_size = args->v0.ram_user = pfb->ram->size; else args->v0.ram_size = args->v0.ram_user = 0; if (imem && args->v0.ram_size > 0) args->v0.ram_user = args->v0.ram_user - imem->reserved; return 0; } static int nvkm_devobj_mthd(struct nvkm_object *object, u32 mthd, void *data, u32 size) { switch (mthd) { case NV_DEVICE_V0_INFO: return nvkm_devobj_info(object, data, size); default: break; } return -EINVAL; } static u8 nvkm_devobj_rd08(struct nvkm_object *object, u64 addr) { return nv_rd08(object->engine, addr); } static u16 nvkm_devobj_rd16(struct nvkm_object *object, u64 addr) { return nv_rd16(object->engine, addr); } static u32 nvkm_devobj_rd32(struct nvkm_object *object, u64 addr) { return nv_rd32(object->engine, addr); } static void nvkm_devobj_wr08(struct nvkm_object *object, u64 addr, u8 data) { nv_wr08(object->engine, addr, data); } static void nvkm_devobj_wr16(struct nvkm_object *object, u64 addr, u16 data) { nv_wr16(object->engine, addr, data); } static void nvkm_devobj_wr32(struct nvkm_object *object, u64 addr, u32 data) { nv_wr32(object->engine, addr, data); } static int nvkm_devobj_map(struct nvkm_object *object, u64 *addr, u32 *size) { struct nvkm_device *device = nv_device(object); *addr = nv_device_resource_start(device, 0); *size = nv_device_resource_len(device, 0); return 0; } static const u64 disable_map[] = { [NVDEV_SUBDEV_VBIOS] = NV_DEVICE_V0_DISABLE_VBIOS, [NVDEV_SUBDEV_DEVINIT] = NV_DEVICE_V0_DISABLE_CORE, [NVDEV_SUBDEV_GPIO] = NV_DEVICE_V0_DISABLE_CORE, [NVDEV_SUBDEV_I2C] = NV_DEVICE_V0_DISABLE_CORE, [NVDEV_SUBDEV_CLK ] = NV_DEVICE_V0_DISABLE_CORE, [NVDEV_SUBDEV_MXM] = NV_DEVICE_V0_DISABLE_CORE, [NVDEV_SUBDEV_MC] = NV_DEVICE_V0_DISABLE_CORE, [NVDEV_SUBDEV_BUS] = NV_DEVICE_V0_DISABLE_CORE, [NVDEV_SUBDEV_TIMER] = NV_DEVICE_V0_DISABLE_CORE, [NVDEV_SUBDEV_FB] = NV_DEVICE_V0_DISABLE_CORE, [NVDEV_SUBDEV_LTC] = NV_DEVICE_V0_DISABLE_CORE, [NVDEV_SUBDEV_IBUS] = NV_DEVICE_V0_DISABLE_CORE, [NVDEV_SUBDEV_INSTMEM] = NV_DEVICE_V0_DISABLE_CORE, [NVDEV_SUBDEV_MMU] = NV_DEVICE_V0_DISABLE_CORE, [NVDEV_SUBDEV_BAR] = NV_DEVICE_V0_DISABLE_CORE, [NVDEV_SUBDEV_VOLT] = NV_DEVICE_V0_DISABLE_CORE, [NVDEV_SUBDEV_THERM] = NV_DEVICE_V0_DISABLE_CORE, [NVDEV_SUBDEV_PMU] = NV_DEVICE_V0_DISABLE_CORE, [NVDEV_SUBDEV_FUSE] = NV_DEVICE_V0_DISABLE_CORE, [NVDEV_ENGINE_DMAOBJ] = NV_DEVICE_V0_DISABLE_CORE, [NVDEV_ENGINE_PM ] = NV_DEVICE_V0_DISABLE_CORE, [NVDEV_ENGINE_FIFO] = NV_DEVICE_V0_DISABLE_FIFO, [NVDEV_ENGINE_SW] = NV_DEVICE_V0_DISABLE_FIFO, [NVDEV_ENGINE_GR] = NV_DEVICE_V0_DISABLE_GR, [NVDEV_ENGINE_MPEG] = NV_DEVICE_V0_DISABLE_MPEG, [NVDEV_ENGINE_ME] = NV_DEVICE_V0_DISABLE_ME, [NVDEV_ENGINE_VP] = NV_DEVICE_V0_DISABLE_VP, [NVDEV_ENGINE_CIPHER] = NV_DEVICE_V0_DISABLE_CIPHER, [NVDEV_ENGINE_BSP] = NV_DEVICE_V0_DISABLE_BSP, [NVDEV_ENGINE_MSPPP] = NV_DEVICE_V0_DISABLE_MSPPP, [NVDEV_ENGINE_CE0] = NV_DEVICE_V0_DISABLE_CE0, [NVDEV_ENGINE_CE1] = NV_DEVICE_V0_DISABLE_CE1, [NVDEV_ENGINE_CE2] = NV_DEVICE_V0_DISABLE_CE2, [NVDEV_ENGINE_VIC] = NV_DEVICE_V0_DISABLE_VIC, [NVDEV_ENGINE_MSENC] = NV_DEVICE_V0_DISABLE_MSENC, [NVDEV_ENGINE_DISP] = NV_DEVICE_V0_DISABLE_DISP, [NVDEV_ENGINE_MSVLD] = NV_DEVICE_V0_DISABLE_MSVLD, [NVDEV_ENGINE_SEC] = NV_DEVICE_V0_DISABLE_SEC, [NVDEV_SUBDEV_NR] = 0, }; static void nvkm_devobj_dtor(struct nvkm_object *object) { struct nvkm_devobj *devobj = (void *)object; int i; for (i = NVDEV_SUBDEV_NR - 1; i >= 0; i--) nvkm_object_ref(NULL, &devobj->subdev[i]); nvkm_parent_destroy(&devobj->base); } static struct nvkm_oclass nvkm_devobj_oclass_super = { .handle = NV_DEVICE, .ofuncs = &(struct nvkm_ofuncs) { .dtor = nvkm_devobj_dtor, .init = _nvkm_parent_init, .fini = _nvkm_parent_fini, .mthd = nvkm_devobj_mthd, .map = nvkm_devobj_map, .rd08 = nvkm_devobj_rd08, .rd16 = nvkm_devobj_rd16, .rd32 = nvkm_devobj_rd32, .wr08 = nvkm_devobj_wr08, .wr16 = nvkm_devobj_wr16, .wr32 = nvkm_devobj_wr32, } }; static int nvkm_devobj_ctor(struct nvkm_object *parent, struct nvkm_object *engine, struct nvkm_oclass *oclass, void *data, u32 size, struct nvkm_object **pobject) { union { struct nv_device_v0 v0; } *args = data; struct nvkm_client *client = nv_client(parent); struct nvkm_device *device; struct nvkm_devobj *devobj; u32 boot0, strap; u64 disable, mmio_base, mmio_size; void __iomem *map; int ret, i, c; nv_ioctl(parent, "create device size %d\n", size); if (nvif_unpack(args->v0, 0, 0, false)) { nv_ioctl(parent, "create device v%d device %016llx " "disable %016llx debug0 %016llx\n", args->v0.version, args->v0.device, args->v0.disable, args->v0.debug0); } else return ret; /* give priviledged clients register access */ if (client->super) oclass = &nvkm_devobj_oclass_super; /* find the device subdev that matches what the client requested */ device = nv_device(client->device); if (args->v0.device != ~0) { device = nvkm_device_find(args->v0.device); if (!device) return -ENODEV; } ret = nvkm_parent_create(parent, nv_object(device), oclass, 0, nvkm_control_oclass, (1ULL << NVDEV_ENGINE_DMAOBJ) | (1ULL << NVDEV_ENGINE_FIFO) | (1ULL << NVDEV_ENGINE_DISP) | (1ULL << NVDEV_ENGINE_PM), &devobj); *pobject = nv_object(devobj); if (ret) return ret; mmio_base = nv_device_resource_start(device, 0); mmio_size = nv_device_resource_len(device, 0); /* translate api disable mask into internal mapping */ disable = args->v0.debug0; for (i = 0; i < NVDEV_SUBDEV_NR; i++) { if (args->v0.disable & disable_map[i]) disable |= (1ULL << i); } /* identify the chipset, and determine classes of subdev/engines */ if (!(args->v0.disable & NV_DEVICE_V0_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); ioread32_native(map); } /* 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 & 0x1f000000) > 0) { device->chipset = (boot0 & 0x1ff00000) >> 20; device->chiprev = (boot0 & 0x000000ff); switch (device->chipset & 0x1f0) { case 0x010: { if (0x461 & (1 << (device->chipset & 0xf))) device->card_type = NV_10; else device->card_type = NV_11; device->chiprev = 0x00; break; } case 0x020: device->card_type = NV_20; break; case 0x030: device->card_type = NV_30; break; case 0x040: case 0x060: device->card_type = NV_40; break; case 0x050: case 0x080: case 0x090: case 0x0a0: device->card_type = NV_50; break; case 0x0c0: case 0x0d0: device->card_type = NV_C0; break; case 0x0e0: case 0x0f0: case 0x100: device->card_type = NV_E0; break; case 0x110: case 0x120: device->card_type = GM100; 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: case NV_11: 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: ret = gf100_identify(device); break; case NV_E0: ret = gk104_identify(device); break; case GM100: ret = gm100_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->card_type <= NV_10 || 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); } else if ( (args->v0.disable & NV_DEVICE_V0_DISABLE_IDENTIFY)) { device->cname = "NULL"; device->oclass[NVDEV_SUBDEV_VBIOS] = &nvkm_bios_oclass; } if (!(args->v0.disable & NV_DEVICE_V0_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 = 1, c = 1; i < NVDEV_SUBDEV_NR; i++) { if (!(oclass = device->oclass[i]) || (disable & (1ULL << i))) continue; if (device->subdev[i]) { nvkm_object_ref(device->subdev[i], &devobj->subdev[i]); continue; } ret = nvkm_object_ctor(nv_object(device), NULL, oclass, NULL, i, &devobj->subdev[i]); if (ret == -ENODEV) continue; if (ret) return ret; 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 nvkm_object *subdev = devobj->subdev[c++]; if (subdev && !nv_iclass(subdev, NV_ENGINE_CLASS)) { ret = nvkm_object_inc(subdev); if (ret) return ret; atomic_dec(&nv_object(device)->usecount); } else if (subdev) { nvkm_subdev_reset(subdev); } } } return 0; } static struct nvkm_ofuncs nvkm_devobj_ofuncs = { .ctor = nvkm_devobj_ctor, .dtor = nvkm_devobj_dtor, .init = _nvkm_parent_init, .fini = _nvkm_parent_fini, .mthd = nvkm_devobj_mthd, }; /****************************************************************************** * nvkm_device: engine functions *****************************************************************************/ struct nvkm_device * nv_device(void *obj) { struct nvkm_object *device = nv_object(obj); if (device->engine == NULL) { while (device && device->parent) device = device->parent; } else { device = &nv_object(obj)->engine->subdev.object; if (device && device->parent) device = device->parent; } #if CONFIG_NOUVEAU_DEBUG >= NV_DBG_PARANOIA if (unlikely(!device)) nv_assert("BAD CAST -> NvDevice, 0x%08x\n", nv_hclass(obj)); #endif return (void *)device; } static struct nvkm_oclass nvkm_device_sclass[] = { { 0x0080, &nvkm_devobj_ofuncs }, {} }; static int nvkm_device_event_ctor(struct nvkm_object *object, void *data, u32 size, struct nvkm_notify *notify) { if (!WARN_ON(size != 0)) { notify->size = 0; notify->types = 1; notify->index = 0; return 0; } return -EINVAL; } static const struct nvkm_event_func nvkm_device_event_func = { .ctor = nvkm_device_event_ctor, }; static int nvkm_device_fini(struct nvkm_object *object, bool suspend) { struct nvkm_device *device = (void *)object; struct nvkm_object *subdev; int ret, i; for (i = NVDEV_SUBDEV_NR - 1; i >= 0; i--) { if ((subdev = device->subdev[i])) { if (!nv_iclass(subdev, NV_ENGINE_CLASS)) { ret = nvkm_object_dec(subdev, suspend); if (ret && suspend) goto fail; } } } ret = nvkm_acpi_fini(device, suspend); fail: for (; ret && i < NVDEV_SUBDEV_NR; i++) { if ((subdev = device->subdev[i])) { if (!nv_iclass(subdev, NV_ENGINE_CLASS)) { ret = nvkm_object_inc(subdev); if (ret) { /* XXX */ } } } } return ret; } static int nvkm_device_init(struct nvkm_object *object) { struct nvkm_device *device = (void *)object; struct nvkm_object *subdev; int ret, i = 0; ret = nvkm_acpi_init(device); if (ret) goto fail; for (i = 0; i < NVDEV_SUBDEV_NR; i++) { if ((subdev = device->subdev[i])) { if (!nv_iclass(subdev, NV_ENGINE_CLASS)) { ret = nvkm_object_inc(subdev); if (ret) goto fail; } else { nvkm_subdev_reset(subdev); } } } ret = 0; fail: for (--i; ret && i >= 0; i--) { if ((subdev = device->subdev[i])) { if (!nv_iclass(subdev, NV_ENGINE_CLASS)) nvkm_object_dec(subdev, false); } } if (ret) nvkm_acpi_fini(device, false); return ret; } static void nvkm_device_dtor(struct nvkm_object *object) { struct nvkm_device *device = (void *)object; nvkm_event_fini(&device->event); mutex_lock(&nv_devices_mutex); list_del(&device->head); mutex_unlock(&nv_devices_mutex); if (nv_subdev(device)->mmio) iounmap(nv_subdev(device)->mmio); nvkm_engine_destroy(&device->engine); } resource_size_t nv_device_resource_start(struct nvkm_device *device, unsigned int bar) { if (nv_device_is_pci(device)) { return pci_resource_start(device->pdev, bar); } else { struct resource *res; res = platform_get_resource(device->platformdev, IORESOURCE_MEM, bar); if (!res) return 0; return res->start; } } resource_size_t nv_device_resource_len(struct nvkm_device *device, unsigned int bar) { if (nv_device_is_pci(device)) { return pci_resource_len(device->pdev, bar); } else { struct resource *res; res = platform_get_resource(device->platformdev, IORESOURCE_MEM, bar); if (!res) return 0; return resource_size(res); } } int nv_device_get_irq(struct nvkm_device *device, bool stall) { if (nv_device_is_pci(device)) { return device->pdev->irq; } else { return platform_get_irq_byname(device->platformdev, stall ? "stall" : "nonstall"); } } static struct nvkm_oclass nvkm_device_oclass = { .handle = NV_ENGINE(DEVICE, 0x00), .ofuncs = &(struct nvkm_ofuncs) { .dtor = nvkm_device_dtor, .init = nvkm_device_init, .fini = nvkm_device_fini, }, }; int nvkm_device_create_(void *dev, enum nv_bus_type type, u64 name, const char *sname, const char *cfg, const char *dbg, int length, void **pobject) { struct nvkm_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 = nvkm_engine_create_(NULL, NULL, &nvkm_device_oclass, true, "DEVICE", "device", length, pobject); device = *pobject; if (ret) goto done; switch (type) { case NVKM_BUS_PCI: device->pdev = dev; break; case NVKM_BUS_PLATFORM: device->platformdev = dev; break; } device->handle = name; device->cfgopt = cfg; device->dbgopt = dbg; device->name = sname; nv_subdev(device)->debug = nvkm_dbgopt(device->dbgopt, "DEVICE"); nv_engine(device)->sclass = nvkm_device_sclass; list_add(&device->head, &nv_devices); ret = nvkm_event_init(&nvkm_device_event_func, 1, 1, &device->event); done: mutex_unlock(&nv_devices_mutex); return ret; }