/* * Copyright 1998-2009 VIA Technologies, Inc. All Rights Reserved. * Copyright 2001-2008 S3 Graphics, Inc. All Rights Reserved. * Copyright 2009 Jonathan Corbet */ /* * Core code for the Via multifunction framebuffer device. */ #include "via-core.h" #include "via_i2c.h" #include "via-gpio.h" #include "global.h" #include #include /* * The default port config. */ static struct via_port_cfg adap_configs[] = { [VIA_PORT_26] = { VIA_PORT_I2C, VIA_MODE_OFF, VIASR, 0x26 }, [VIA_PORT_31] = { VIA_PORT_I2C, VIA_MODE_I2C, VIASR, 0x31 }, [VIA_PORT_25] = { VIA_PORT_GPIO, VIA_MODE_GPIO, VIASR, 0x25 }, [VIA_PORT_2C] = { VIA_PORT_GPIO, VIA_MODE_I2C, VIASR, 0x2c }, [VIA_PORT_3D] = { VIA_PORT_GPIO, VIA_MODE_GPIO, VIASR, 0x3d }, { 0, 0, 0, 0 } }; /* * We currently only support one viafb device (will there ever be * more than one?), so just declare it globally here. */ static struct viafb_dev global_dev; /* * Basic register access; spinlock required. */ static inline void viafb_mmio_write(int reg, u32 v) { iowrite32(v, global_dev.engine_mmio + reg); } static inline int viafb_mmio_read(int reg) { return ioread32(global_dev.engine_mmio + reg); } /* ---------------------------------------------------------------------- */ /* * Interrupt management. We have a single IRQ line for a lot of * different functions, so we need to share it. The design here * is that we don't want to reimplement the shared IRQ code here; * we also want to avoid having contention for a single handler thread. * So each subdev driver which needs interrupts just requests * them directly from the kernel. We just have what's needed for * overall access to the interrupt control register. */ /* * Which interrupts are enabled now? */ static u32 viafb_enabled_ints; static void viafb_int_init(void) { viafb_enabled_ints = 0; viafb_mmio_write(VDE_INTERRUPT, 0); } /* * Allow subdevs to ask for specific interrupts to be enabled. These * functions must be called with reg_lock held */ void viafb_irq_enable(u32 mask) { viafb_enabled_ints |= mask; viafb_mmio_write(VDE_INTERRUPT, viafb_enabled_ints | VDE_I_ENABLE); } EXPORT_SYMBOL_GPL(viafb_irq_enable); void viafb_irq_disable(u32 mask) { viafb_enabled_ints &= ~mask; if (viafb_enabled_ints == 0) viafb_mmio_write(VDE_INTERRUPT, 0); /* Disable entirely */ else viafb_mmio_write(VDE_INTERRUPT, viafb_enabled_ints | VDE_I_ENABLE); } EXPORT_SYMBOL_GPL(viafb_irq_disable); /* * Figure out how big our framebuffer memory is. Kind of ugly, * but evidently we can't trust the information found in the * fbdev configuration area. */ static u16 via_function3[] = { CLE266_FUNCTION3, KM400_FUNCTION3, CN400_FUNCTION3, CN700_FUNCTION3, CX700_FUNCTION3, KM800_FUNCTION3, KM890_FUNCTION3, P4M890_FUNCTION3, P4M900_FUNCTION3, VX800_FUNCTION3, VX855_FUNCTION3, }; /* Get the BIOS-configured framebuffer size from PCI configuration space * of function 3 in the respective chipset */ static int viafb_get_fb_size_from_pci(int chip_type) { int i; u8 offset = 0; u32 FBSize; u32 VideoMemSize; /* search for the "FUNCTION3" device in this chipset */ for (i = 0; i < ARRAY_SIZE(via_function3); i++) { struct pci_dev *pdev; pdev = pci_get_device(PCI_VENDOR_ID_VIA, via_function3[i], NULL); if (!pdev) continue; DEBUG_MSG(KERN_INFO "Device ID = %x\n", pdev->device); switch (pdev->device) { case CLE266_FUNCTION3: case KM400_FUNCTION3: offset = 0xE0; break; case CN400_FUNCTION3: case CN700_FUNCTION3: case CX700_FUNCTION3: case KM800_FUNCTION3: case KM890_FUNCTION3: case P4M890_FUNCTION3: case P4M900_FUNCTION3: case VX800_FUNCTION3: case VX855_FUNCTION3: /*case CN750_FUNCTION3: */ offset = 0xA0; break; } if (!offset) break; pci_read_config_dword(pdev, offset, &FBSize); pci_dev_put(pdev); } if (!offset) { printk(KERN_ERR "cannot determine framebuffer size\n"); return -EIO; } FBSize = FBSize & 0x00007000; DEBUG_MSG(KERN_INFO "FB Size = %x\n", FBSize); if (chip_type < UNICHROME_CX700) { switch (FBSize) { case 0x00004000: VideoMemSize = (16 << 20); /*16M */ break; case 0x00005000: VideoMemSize = (32 << 20); /*32M */ break; case 0x00006000: VideoMemSize = (64 << 20); /*64M */ break; default: VideoMemSize = (32 << 20); /*32M */ break; } } else { switch (FBSize) { case 0x00001000: VideoMemSize = (8 << 20); /*8M */ break; case 0x00002000: VideoMemSize = (16 << 20); /*16M */ break; case 0x00003000: VideoMemSize = (32 << 20); /*32M */ break; case 0x00004000: VideoMemSize = (64 << 20); /*64M */ break; case 0x00005000: VideoMemSize = (128 << 20); /*128M */ break; case 0x00006000: VideoMemSize = (256 << 20); /*256M */ break; case 0x00007000: /* Only on VX855/875 */ VideoMemSize = (512 << 20); /*512M */ break; default: VideoMemSize = (32 << 20); /*32M */ break; } } return VideoMemSize; } /* * Figure out and map our MMIO regions. */ static int __devinit via_pci_setup_mmio(struct viafb_dev *vdev) { /* * Hook up to the device registers. */ vdev->engine_start = pci_resource_start(vdev->pdev, 1); vdev->engine_len = pci_resource_len(vdev->pdev, 1); /* If this fails, others will notice later */ vdev->engine_mmio = ioremap_nocache(vdev->engine_start, vdev->engine_len); /* * Likewise with I/O memory. */ vdev->fbmem_start = pci_resource_start(vdev->pdev, 0); vdev->fbmem_len = viafb_get_fb_size_from_pci(vdev->chip_type); if (vdev->fbmem_len < 0) return vdev->fbmem_len; vdev->fbmem = ioremap_nocache(vdev->fbmem_start, vdev->fbmem_len); if (vdev->fbmem == NULL) return -ENOMEM; return 0; } static void __devexit via_pci_teardown_mmio(struct viafb_dev *vdev) { iounmap(vdev->fbmem); iounmap(vdev->engine_mmio); } /* * Create our subsidiary devices. */ static struct viafb_subdev_info { char *name; struct platform_device *platdev; } viafb_subdevs[] = { { .name = "viafb-gpio", }, { .name = "viafb-i2c", } }; #define N_SUBDEVS ARRAY_SIZE(viafb_subdevs) static int __devinit via_create_subdev(struct viafb_dev *vdev, struct viafb_subdev_info *info) { int ret; info->platdev = platform_device_alloc(info->name, -1); if (!info->platdev) { dev_err(&vdev->pdev->dev, "Unable to allocate pdev %s\n", info->name); return -ENOMEM; } info->platdev->dev.parent = &vdev->pdev->dev; info->platdev->dev.platform_data = vdev; ret = platform_device_add(info->platdev); if (ret) { dev_err(&vdev->pdev->dev, "Unable to add pdev %s\n", info->name); platform_device_put(info->platdev); info->platdev = NULL; } return ret; } static int __devinit via_setup_subdevs(struct viafb_dev *vdev) { int i; /* * Ignore return values. Even if some of the devices * fail to be created, we'll still be able to use some * of the rest. */ for (i = 0; i < N_SUBDEVS; i++) via_create_subdev(vdev, viafb_subdevs + i); return 0; } static void __devexit via_teardown_subdevs(void) { int i; for (i = 0; i < N_SUBDEVS; i++) if (viafb_subdevs[i].platdev) { viafb_subdevs[i].platdev->dev.platform_data = NULL; platform_device_unregister(viafb_subdevs[i].platdev); } } static int __devinit via_pci_probe(struct pci_dev *pdev, const struct pci_device_id *ent) { int ret; ret = pci_enable_device(pdev); if (ret) return ret; /* * Global device initialization. */ memset(&global_dev, 0, sizeof(global_dev)); global_dev.pdev = pdev; global_dev.chip_type = ent->driver_data; global_dev.port_cfg = adap_configs; spin_lock_init(&global_dev.reg_lock); ret = via_pci_setup_mmio(&global_dev); if (ret) goto out_disable; /* * Set up interrupts and create our subdevices. Continue even if * some things fail. */ viafb_int_init(); via_setup_subdevs(&global_dev); /* * Set up the framebuffer. */ ret = via_fb_pci_probe(&global_dev); if (ret) goto out_subdevs; return 0; out_subdevs: via_teardown_subdevs(); via_pci_teardown_mmio(&global_dev); out_disable: pci_disable_device(pdev); return ret; } static void __devexit via_pci_remove(struct pci_dev *pdev) { via_teardown_subdevs(); via_fb_pci_remove(pdev); via_pci_teardown_mmio(&global_dev); pci_disable_device(pdev); } static struct pci_device_id via_pci_table[] __devinitdata = { { PCI_DEVICE(PCI_VENDOR_ID_VIA, UNICHROME_CLE266_DID), .driver_data = UNICHROME_CLE266 }, { PCI_DEVICE(PCI_VENDOR_ID_VIA, UNICHROME_PM800_DID), .driver_data = UNICHROME_PM800 }, { PCI_DEVICE(PCI_VENDOR_ID_VIA, UNICHROME_K400_DID), .driver_data = UNICHROME_K400 }, { PCI_DEVICE(PCI_VENDOR_ID_VIA, UNICHROME_K800_DID), .driver_data = UNICHROME_K800 }, { PCI_DEVICE(PCI_VENDOR_ID_VIA, UNICHROME_P4M890_DID), .driver_data = UNICHROME_CN700 }, { PCI_DEVICE(PCI_VENDOR_ID_VIA, UNICHROME_K8M890_DID), .driver_data = UNICHROME_K8M890 }, { PCI_DEVICE(PCI_VENDOR_ID_VIA, UNICHROME_CX700_DID), .driver_data = UNICHROME_CX700 }, { PCI_DEVICE(PCI_VENDOR_ID_VIA, UNICHROME_P4M900_DID), .driver_data = UNICHROME_P4M900 }, { PCI_DEVICE(PCI_VENDOR_ID_VIA, UNICHROME_CN750_DID), .driver_data = UNICHROME_CN750 }, { PCI_DEVICE(PCI_VENDOR_ID_VIA, UNICHROME_VX800_DID), .driver_data = UNICHROME_VX800 }, { PCI_DEVICE(PCI_VENDOR_ID_VIA, UNICHROME_VX855_DID), .driver_data = UNICHROME_VX855 }, { } }; MODULE_DEVICE_TABLE(pci, via_pci_table); static struct pci_driver via_driver = { .name = "viafb", .id_table = via_pci_table, .probe = via_pci_probe, .remove = __devexit_p(via_pci_remove), }; static int __init via_core_init(void) { int ret; ret = viafb_init(); if (ret) return ret; viafb_i2c_init(); viafb_gpio_init(); return pci_register_driver(&via_driver); } static void __exit via_core_exit(void) { pci_unregister_driver(&via_driver); viafb_gpio_exit(); viafb_i2c_exit(); viafb_exit(); } module_init(via_core_init); module_exit(via_core_exit);