|  | #include <linux/export.h> | 
|  | #include <linux/kernel.h> | 
|  | #include <linux/init.h> | 
|  | #include <linux/slab.h> | 
|  |  | 
|  | #include <asm/addrspace.h> | 
|  | #include <asm/paccess.h> | 
|  | #include <asm/gio_device.h> | 
|  | #include <asm/sgi/gio.h> | 
|  | #include <asm/sgi/hpc3.h> | 
|  | #include <asm/sgi/mc.h> | 
|  | #include <asm/sgi/ip22.h> | 
|  |  | 
|  | static struct bus_type gio_bus_type; | 
|  |  | 
|  | static struct { | 
|  | const char *name; | 
|  | __u8	   id; | 
|  | } gio_name_table[] = { | 
|  | { .name = "SGI Impact", .id = 0x10 }, | 
|  | { .name = "Phobos G160", .id = 0x35 }, | 
|  | /* fake IDs */ | 
|  | { .name = "SGI Newport", .id = 0x7e }, | 
|  | { .name = "SGI GR2/GR3", .id = 0x7f }, | 
|  | }; | 
|  |  | 
|  | static struct device gio_bus = { | 
|  | .init_name = "gio", | 
|  | }; | 
|  |  | 
|  | /** | 
|  | * gio_match_device - Tell if an of_device structure has a matching | 
|  | * gio_match structure | 
|  | * @ids: array of of device match structures to search in | 
|  | * @dev: the of device structure to match against | 
|  | * | 
|  | * Used by a driver to check whether an of_device present in the | 
|  | * system is in its list of supported devices. | 
|  | */ | 
|  | const struct gio_device_id *gio_match_device(const struct gio_device_id *match, | 
|  | const struct gio_device *dev) | 
|  | { | 
|  | const struct gio_device_id *ids; | 
|  |  | 
|  | for (ids = match; ids->id != 0xff; ids++) | 
|  | if (ids->id == dev->id.id) | 
|  | return ids; | 
|  |  | 
|  | return NULL; | 
|  | } | 
|  | EXPORT_SYMBOL_GPL(gio_match_device); | 
|  |  | 
|  | struct gio_device *gio_dev_get(struct gio_device *dev) | 
|  | { | 
|  | struct device *tmp; | 
|  |  | 
|  | if (!dev) | 
|  | return NULL; | 
|  | tmp = get_device(&dev->dev); | 
|  | if (tmp) | 
|  | return to_gio_device(tmp); | 
|  | else | 
|  | return NULL; | 
|  | } | 
|  | EXPORT_SYMBOL_GPL(gio_dev_get); | 
|  |  | 
|  | void gio_dev_put(struct gio_device *dev) | 
|  | { | 
|  | if (dev) | 
|  | put_device(&dev->dev); | 
|  | } | 
|  | EXPORT_SYMBOL_GPL(gio_dev_put); | 
|  |  | 
|  | /** | 
|  | * gio_release_dev - free an gio device structure when all users of it are finished. | 
|  | * @dev: device that's been disconnected | 
|  | * | 
|  | * Will be called only by the device core when all users of this gio device are | 
|  | * done. | 
|  | */ | 
|  | void gio_release_dev(struct device *dev) | 
|  | { | 
|  | struct gio_device *giodev; | 
|  |  | 
|  | giodev = to_gio_device(dev); | 
|  | kfree(giodev); | 
|  | } | 
|  | EXPORT_SYMBOL_GPL(gio_release_dev); | 
|  |  | 
|  | int gio_device_register(struct gio_device *giodev) | 
|  | { | 
|  | giodev->dev.bus = &gio_bus_type; | 
|  | giodev->dev.parent = &gio_bus; | 
|  | return device_register(&giodev->dev); | 
|  | } | 
|  | EXPORT_SYMBOL_GPL(gio_device_register); | 
|  |  | 
|  | void gio_device_unregister(struct gio_device *giodev) | 
|  | { | 
|  | device_unregister(&giodev->dev); | 
|  | } | 
|  | EXPORT_SYMBOL_GPL(gio_device_unregister); | 
|  |  | 
|  | static int gio_bus_match(struct device *dev, struct device_driver *drv) | 
|  | { | 
|  | struct gio_device *gio_dev = to_gio_device(dev); | 
|  | struct gio_driver *gio_drv = to_gio_driver(drv); | 
|  |  | 
|  | return gio_match_device(gio_drv->id_table, gio_dev) != NULL; | 
|  | } | 
|  |  | 
|  | static int gio_device_probe(struct device *dev) | 
|  | { | 
|  | int error = -ENODEV; | 
|  | struct gio_driver *drv; | 
|  | struct gio_device *gio_dev; | 
|  | const struct gio_device_id *match; | 
|  |  | 
|  | drv = to_gio_driver(dev->driver); | 
|  | gio_dev = to_gio_device(dev); | 
|  |  | 
|  | if (!drv->probe) | 
|  | return error; | 
|  |  | 
|  | gio_dev_get(gio_dev); | 
|  |  | 
|  | match = gio_match_device(drv->id_table, gio_dev); | 
|  | if (match) | 
|  | error = drv->probe(gio_dev, match); | 
|  | if (error) | 
|  | gio_dev_put(gio_dev); | 
|  |  | 
|  | return error; | 
|  | } | 
|  |  | 
|  | static int gio_device_remove(struct device *dev) | 
|  | { | 
|  | struct gio_device *gio_dev = to_gio_device(dev); | 
|  | struct gio_driver *drv = to_gio_driver(dev->driver); | 
|  |  | 
|  | if (dev->driver && drv->remove) | 
|  | drv->remove(gio_dev); | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static int gio_device_suspend(struct device *dev, pm_message_t state) | 
|  | { | 
|  | struct gio_device *gio_dev = to_gio_device(dev); | 
|  | struct gio_driver *drv = to_gio_driver(dev->driver); | 
|  | int error = 0; | 
|  |  | 
|  | if (dev->driver && drv->suspend) | 
|  | error = drv->suspend(gio_dev, state); | 
|  | return error; | 
|  | } | 
|  |  | 
|  | static int gio_device_resume(struct device *dev) | 
|  | { | 
|  | struct gio_device *gio_dev = to_gio_device(dev); | 
|  | struct gio_driver *drv = to_gio_driver(dev->driver); | 
|  | int error = 0; | 
|  |  | 
|  | if (dev->driver && drv->resume) | 
|  | error = drv->resume(gio_dev); | 
|  | return error; | 
|  | } | 
|  |  | 
|  | static void gio_device_shutdown(struct device *dev) | 
|  | { | 
|  | struct gio_device *gio_dev = to_gio_device(dev); | 
|  | struct gio_driver *drv = to_gio_driver(dev->driver); | 
|  |  | 
|  | if (dev->driver && drv->shutdown) | 
|  | drv->shutdown(gio_dev); | 
|  | } | 
|  |  | 
|  | static ssize_t modalias_show(struct device *dev, struct device_attribute *a, | 
|  | char *buf) | 
|  | { | 
|  | struct gio_device *gio_dev = to_gio_device(dev); | 
|  | int len = snprintf(buf, PAGE_SIZE, "gio:%x\n", gio_dev->id.id); | 
|  |  | 
|  | return (len >= PAGE_SIZE) ? (PAGE_SIZE - 1) : len; | 
|  | } | 
|  |  | 
|  | static ssize_t name_show(struct device *dev, | 
|  | struct device_attribute *attr, char *buf) | 
|  | { | 
|  | struct gio_device *giodev; | 
|  |  | 
|  | giodev = to_gio_device(dev); | 
|  | return sprintf(buf, "%s", giodev->name); | 
|  | } | 
|  |  | 
|  | static ssize_t id_show(struct device *dev, | 
|  | struct device_attribute *attr, char *buf) | 
|  | { | 
|  | struct gio_device *giodev; | 
|  |  | 
|  | giodev = to_gio_device(dev); | 
|  | return sprintf(buf, "%x", giodev->id.id); | 
|  | } | 
|  |  | 
|  | static struct device_attribute gio_dev_attrs[] = { | 
|  | __ATTR_RO(modalias), | 
|  | __ATTR_RO(name), | 
|  | __ATTR_RO(id), | 
|  | __ATTR_NULL, | 
|  | }; | 
|  |  | 
|  | static int gio_device_uevent(struct device *dev, struct kobj_uevent_env *env) | 
|  | { | 
|  | struct gio_device *gio_dev = to_gio_device(dev); | 
|  |  | 
|  | add_uevent_var(env, "MODALIAS=gio:%x", gio_dev->id.id); | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | int gio_register_driver(struct gio_driver *drv) | 
|  | { | 
|  | /* initialize common driver fields */ | 
|  | if (!drv->driver.name) | 
|  | drv->driver.name = drv->name; | 
|  | if (!drv->driver.owner) | 
|  | drv->driver.owner = drv->owner; | 
|  | drv->driver.bus = &gio_bus_type; | 
|  |  | 
|  | /* register with core */ | 
|  | return driver_register(&drv->driver); | 
|  | } | 
|  | EXPORT_SYMBOL_GPL(gio_register_driver); | 
|  |  | 
|  | void gio_unregister_driver(struct gio_driver *drv) | 
|  | { | 
|  | driver_unregister(&drv->driver); | 
|  | } | 
|  | EXPORT_SYMBOL_GPL(gio_unregister_driver); | 
|  |  | 
|  | void gio_set_master(struct gio_device *dev) | 
|  | { | 
|  | u32 tmp = sgimc->giopar; | 
|  |  | 
|  | switch (dev->slotno) { | 
|  | case 0: | 
|  | tmp |= SGIMC_GIOPAR_MASTERGFX; | 
|  | break; | 
|  | case 1: | 
|  | tmp |= SGIMC_GIOPAR_MASTEREXP0; | 
|  | break; | 
|  | case 2: | 
|  | tmp |= SGIMC_GIOPAR_MASTEREXP1; | 
|  | break; | 
|  | } | 
|  | sgimc->giopar = tmp; | 
|  | } | 
|  | EXPORT_SYMBOL_GPL(gio_set_master); | 
|  |  | 
|  | void ip22_gio_set_64bit(int slotno) | 
|  | { | 
|  | u32 tmp = sgimc->giopar; | 
|  |  | 
|  | switch (slotno) { | 
|  | case 0: | 
|  | tmp |= SGIMC_GIOPAR_GFX64; | 
|  | break; | 
|  | case 1: | 
|  | tmp |= SGIMC_GIOPAR_EXP064; | 
|  | break; | 
|  | case 2: | 
|  | tmp |= SGIMC_GIOPAR_EXP164; | 
|  | break; | 
|  | } | 
|  | sgimc->giopar = tmp; | 
|  | } | 
|  |  | 
|  | static int ip22_gio_id(unsigned long addr, u32 *res) | 
|  | { | 
|  | u8 tmp8; | 
|  | u8 tmp16; | 
|  | u32 tmp32; | 
|  | u8 *ptr8; | 
|  | u16 *ptr16; | 
|  | u32 *ptr32; | 
|  |  | 
|  | ptr32 = (void *)CKSEG1ADDR(addr); | 
|  | if (!get_dbe(tmp32, ptr32)) { | 
|  | /* | 
|  | * We got no DBE, but this doesn't mean anything. | 
|  | * If GIO is pipelined (which can't be disabled | 
|  | * for GFX slot) we don't get a DBE, but we see | 
|  | * the transfer size as data. So we do an 8bit | 
|  | * and a 16bit access and check whether the common | 
|  | * data matches | 
|  | */ | 
|  | ptr8 = (void *)CKSEG1ADDR(addr + 3); | 
|  | get_dbe(tmp8, ptr8); | 
|  | ptr16 = (void *)CKSEG1ADDR(addr + 2); | 
|  | get_dbe(tmp16, ptr16); | 
|  | if (tmp8 == (tmp16 & 0xff) && | 
|  | tmp8 == (tmp32 & 0xff) && | 
|  | tmp16 == (tmp32 & 0xffff)) { | 
|  | *res = tmp32; | 
|  | return 1; | 
|  | } | 
|  | } | 
|  | return 0; /* nothing here */ | 
|  | } | 
|  |  | 
|  | #define HQ2_MYSTERY_OFFS       0x6A07C | 
|  | #define NEWPORT_USTATUS_OFFS   0xF133C | 
|  |  | 
|  | static int ip22_is_gr2(unsigned long addr) | 
|  | { | 
|  | u32 tmp; | 
|  | u32 *ptr; | 
|  |  | 
|  | /* HQ2 only allows 32bit accesses */ | 
|  | ptr = (void *)CKSEG1ADDR(addr + HQ2_MYSTERY_OFFS); | 
|  | if (!get_dbe(tmp, ptr)) { | 
|  | if (tmp == 0xdeadbeef) | 
|  | return 1; | 
|  | } | 
|  | return 0; | 
|  | } | 
|  |  | 
|  |  | 
|  | static void ip22_check_gio(int slotno, unsigned long addr) | 
|  | { | 
|  | const char *name = "Unknown"; | 
|  | struct gio_device *gio_dev; | 
|  | u32 tmp; | 
|  | __u8 id; | 
|  | int i; | 
|  |  | 
|  | /* first look for GR2/GR3 by checking mystery register */ | 
|  | if (ip22_is_gr2(addr)) | 
|  | tmp = 0x7f; | 
|  | else { | 
|  | if (!ip22_gio_id(addr, &tmp)) { | 
|  | /* | 
|  | * no GIO signature at start address of slot, but | 
|  | * Newport doesn't have one, so let's check usea | 
|  | * status register | 
|  | */ | 
|  | if (ip22_gio_id(addr + NEWPORT_USTATUS_OFFS, &tmp)) | 
|  | tmp = 0x7e; | 
|  | else | 
|  | tmp = 0; | 
|  | } | 
|  | } | 
|  | if (tmp) { | 
|  | id = GIO_ID(tmp); | 
|  | if (tmp & GIO_32BIT_ID) { | 
|  | if (tmp & GIO_64BIT_IFACE) | 
|  | ip22_gio_set_64bit(slotno); | 
|  | } | 
|  | for (i = 0; i < ARRAY_SIZE(gio_name_table); i++) { | 
|  | if (id == gio_name_table[i].id) { | 
|  | name = gio_name_table[i].name; | 
|  | break; | 
|  | } | 
|  | } | 
|  | printk(KERN_INFO "GIO: slot %d : %s (id %x)\n", | 
|  | slotno, name, id); | 
|  | gio_dev = kzalloc(sizeof *gio_dev, GFP_KERNEL); | 
|  | gio_dev->name = name; | 
|  | gio_dev->slotno = slotno; | 
|  | gio_dev->id.id = id; | 
|  | gio_dev->resource.start = addr; | 
|  | gio_dev->resource.end = addr + 0x3fffff; | 
|  | gio_dev->resource.flags = IORESOURCE_MEM; | 
|  | dev_set_name(&gio_dev->dev, "%d", slotno); | 
|  | gio_device_register(gio_dev); | 
|  | } else | 
|  | printk(KERN_INFO "GIO: slot %d : Empty\n", slotno); | 
|  | } | 
|  |  | 
|  | static struct bus_type gio_bus_type = { | 
|  | .name	   = "gio", | 
|  | .dev_attrs = gio_dev_attrs, | 
|  | .match	   = gio_bus_match, | 
|  | .probe	   = gio_device_probe, | 
|  | .remove	   = gio_device_remove, | 
|  | .suspend   = gio_device_suspend, | 
|  | .resume	   = gio_device_resume, | 
|  | .shutdown  = gio_device_shutdown, | 
|  | .uevent	   = gio_device_uevent, | 
|  | }; | 
|  |  | 
|  | static struct resource gio_bus_resource = { | 
|  | .start = GIO_SLOT_GFX_BASE, | 
|  | .end   = GIO_SLOT_GFX_BASE + 0x9fffff, | 
|  | .name  = "GIO Bus", | 
|  | .flags = IORESOURCE_MEM, | 
|  | }; | 
|  |  | 
|  | int __init ip22_gio_init(void) | 
|  | { | 
|  | unsigned int pbdma __maybe_unused; | 
|  | int ret; | 
|  |  | 
|  | ret = device_register(&gio_bus); | 
|  | if (ret) | 
|  | return ret; | 
|  |  | 
|  | ret = bus_register(&gio_bus_type); | 
|  | if (!ret) { | 
|  | request_resource(&iomem_resource, &gio_bus_resource); | 
|  | printk(KERN_INFO "GIO: Probing bus...\n"); | 
|  |  | 
|  | if (ip22_is_fullhouse() || | 
|  | !get_dbe(pbdma, (unsigned int *)&hpc3c1->pbdma[1])) { | 
|  | /* Indigo2 and ChallengeS */ | 
|  | ip22_check_gio(0, GIO_SLOT_GFX_BASE); | 
|  | ip22_check_gio(1, GIO_SLOT_EXP0_BASE); | 
|  | } else { | 
|  | /* Indy */ | 
|  | ip22_check_gio(0, GIO_SLOT_GFX_BASE); | 
|  | ip22_check_gio(1, GIO_SLOT_EXP0_BASE); | 
|  | ip22_check_gio(2, GIO_SLOT_EXP1_BASE); | 
|  | } | 
|  | } else | 
|  | device_unregister(&gio_bus); | 
|  |  | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | subsys_initcall(ip22_gio_init); |