diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 18:49:45 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 18:49:45 +0000 |
commit | 2c3c1048746a4622d8c89a29670120dc8fab93c4 (patch) | |
tree | 848558de17fb3008cdf4d861b01ac7781903ce39 /drivers/video/fbdev/via/via-core.c | |
parent | Initial commit. (diff) | |
download | linux-2c3c1048746a4622d8c89a29670120dc8fab93c4.tar.xz linux-2c3c1048746a4622d8c89a29670120dc8fab93c4.zip |
Adding upstream version 6.1.76.upstream/6.1.76upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'drivers/video/fbdev/via/via-core.c')
-rw-r--r-- | drivers/video/fbdev/via/via-core.c | 752 |
1 files changed, 752 insertions, 0 deletions
diff --git a/drivers/video/fbdev/via/via-core.c b/drivers/video/fbdev/via/via-core.c new file mode 100644 index 000000000..b8cd04def --- /dev/null +++ b/drivers/video/fbdev/via/via-core.c @@ -0,0 +1,752 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright 1998-2009 VIA Technologies, Inc. All Rights Reserved. + * Copyright 2001-2008 S3 Graphics, Inc. All Rights Reserved. + * Copyright 2009 Jonathan Corbet <corbet@lwn.net> + */ + +/* + * Core code for the Via multifunction framebuffer device. + */ +#include <linux/aperture.h> +#include <linux/via-core.h> +#include <linux/via_i2c.h> +#include <linux/via-gpio.h> +#include "global.h" + +#include <linux/module.h> +#include <linux/interrupt.h> +#include <linux/platform_device.h> +#include <linux/list.h> +#include <linux/pm.h> + +/* + * The default port config. + */ +static struct via_port_cfg adap_configs[] = { + [VIA_PORT_26] = { VIA_PORT_I2C, VIA_MODE_I2C, 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 } +}; + +/* + * The OLPC XO-1.5 puts the camera power and reset lines onto + * GPIO 2C. + */ +static struct via_port_cfg olpc_adap_configs[] = { + [VIA_PORT_26] = { VIA_PORT_I2C, VIA_MODE_I2C, 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_GPIO, 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); + +/* ---------------------------------------------------------------------- */ +/* + * Currently, the camera driver is the only user of the DMA code, so we + * only compile it in if the camera driver is being built. Chances are, + * most viafb systems will not need to have this extra code for a while. + * As soon as another user comes long, the ifdef can be removed. + */ +#if IS_ENABLED(CONFIG_VIDEO_VIA_CAMERA) +/* + * Access to the DMA engine. This currently provides what the camera + * driver needs (i.e. outgoing only) but is easily expandable if need + * be. + */ + +/* + * There are four DMA channels in the vx855. For now, we only + * use one of them, though. Most of the time, the DMA channel + * will be idle, so we keep the IRQ handler unregistered except + * when some subsystem has indicated an interest. + */ +static int viafb_dma_users; +static DECLARE_COMPLETION(viafb_dma_completion); +/* + * This mutex protects viafb_dma_users and our global interrupt + * registration state; it also serializes access to the DMA + * engine. + */ +static DEFINE_MUTEX(viafb_dma_lock); + +/* + * The VX855 DMA descriptor (used for s/g transfers) looks + * like this. + */ +struct viafb_vx855_dma_descr { + u32 addr_low; /* Low part of phys addr */ + u32 addr_high; /* High 12 bits of addr */ + u32 fb_offset; /* Offset into FB memory */ + u32 seg_size; /* Size, 16-byte units */ + u32 tile_mode; /* "tile mode" setting */ + u32 next_desc_low; /* Next descriptor addr */ + u32 next_desc_high; + u32 pad; /* Fill out to 64 bytes */ +}; + +/* + * Flags added to the "next descriptor low" pointers + */ +#define VIAFB_DMA_MAGIC 0x01 /* ??? Just has to be there */ +#define VIAFB_DMA_FINAL_SEGMENT 0x02 /* Final segment */ + +/* + * The completion IRQ handler. + */ +static irqreturn_t viafb_dma_irq(int irq, void *data) +{ + int csr; + irqreturn_t ret = IRQ_NONE; + + spin_lock(&global_dev.reg_lock); + csr = viafb_mmio_read(VDMA_CSR0); + if (csr & VDMA_C_DONE) { + viafb_mmio_write(VDMA_CSR0, VDMA_C_DONE); + complete(&viafb_dma_completion); + ret = IRQ_HANDLED; + } + spin_unlock(&global_dev.reg_lock); + return ret; +} + +/* + * Indicate a need for DMA functionality. + */ +int viafb_request_dma(void) +{ + int ret = 0; + + /* + * Only VX855 is supported currently. + */ + if (global_dev.chip_type != UNICHROME_VX855) + return -ENODEV; + /* + * Note the new user and set up our interrupt handler + * if need be. + */ + mutex_lock(&viafb_dma_lock); + viafb_dma_users++; + if (viafb_dma_users == 1) { + ret = request_irq(global_dev.pdev->irq, viafb_dma_irq, + IRQF_SHARED, "via-dma", &viafb_dma_users); + if (ret) + viafb_dma_users--; + else + viafb_irq_enable(VDE_I_DMA0TDEN); + } + mutex_unlock(&viafb_dma_lock); + return ret; +} +EXPORT_SYMBOL_GPL(viafb_request_dma); + +void viafb_release_dma(void) +{ + mutex_lock(&viafb_dma_lock); + viafb_dma_users--; + if (viafb_dma_users == 0) { + viafb_irq_disable(VDE_I_DMA0TDEN); + free_irq(global_dev.pdev->irq, &viafb_dma_users); + } + mutex_unlock(&viafb_dma_lock); +} +EXPORT_SYMBOL_GPL(viafb_release_dma); + +/* + * Do a scatter/gather DMA copy from FB memory. You must have done + * a successful call to viafb_request_dma() first. + */ +int viafb_dma_copy_out_sg(unsigned int offset, struct scatterlist *sg, int nsg) +{ + struct viafb_vx855_dma_descr *descr; + void *descrpages; + dma_addr_t descr_handle; + unsigned long flags; + int i; + struct scatterlist *sgentry; + dma_addr_t nextdesc; + + /* + * Get a place to put the descriptors. + */ + descrpages = dma_alloc_coherent(&global_dev.pdev->dev, + nsg*sizeof(struct viafb_vx855_dma_descr), + &descr_handle, GFP_KERNEL); + if (descrpages == NULL) { + dev_err(&global_dev.pdev->dev, "Unable to get descr page.\n"); + return -ENOMEM; + } + mutex_lock(&viafb_dma_lock); + /* + * Fill them in. + */ + descr = descrpages; + nextdesc = descr_handle + sizeof(struct viafb_vx855_dma_descr); + for_each_sg(sg, sgentry, nsg, i) { + dma_addr_t paddr = sg_dma_address(sgentry); + descr->addr_low = paddr & 0xfffffff0; + descr->addr_high = ((u64) paddr >> 32) & 0x0fff; + descr->fb_offset = offset; + descr->seg_size = sg_dma_len(sgentry) >> 4; + descr->tile_mode = 0; + descr->next_desc_low = (nextdesc&0xfffffff0) | VIAFB_DMA_MAGIC; + descr->next_desc_high = ((u64) nextdesc >> 32) & 0x0fff; + descr->pad = 0xffffffff; /* VIA driver does this */ + offset += sg_dma_len(sgentry); + nextdesc += sizeof(struct viafb_vx855_dma_descr); + descr++; + } + descr[-1].next_desc_low = VIAFB_DMA_FINAL_SEGMENT|VIAFB_DMA_MAGIC; + /* + * Program the engine. + */ + spin_lock_irqsave(&global_dev.reg_lock, flags); + init_completion(&viafb_dma_completion); + viafb_mmio_write(VDMA_DQWCR0, 0); + viafb_mmio_write(VDMA_CSR0, VDMA_C_ENABLE|VDMA_C_DONE); + viafb_mmio_write(VDMA_MR0, VDMA_MR_TDIE | VDMA_MR_CHAIN); + viafb_mmio_write(VDMA_DPRL0, descr_handle | VIAFB_DMA_MAGIC); + viafb_mmio_write(VDMA_DPRH0, + (((u64)descr_handle >> 32) & 0x0fff) | 0xf0000); + (void) viafb_mmio_read(VDMA_CSR0); + viafb_mmio_write(VDMA_CSR0, VDMA_C_ENABLE|VDMA_C_START); + spin_unlock_irqrestore(&global_dev.reg_lock, flags); + /* + * Now we just wait until the interrupt handler says + * we're done. Except that, actually, we need to wait a little + * longer: the interrupts seem to jump the gun a little and we + * get corrupted frames sometimes. + */ + wait_for_completion_timeout(&viafb_dma_completion, 1); + msleep(1); + if ((viafb_mmio_read(VDMA_CSR0)&VDMA_C_DONE) == 0) + printk(KERN_ERR "VIA DMA timeout!\n"); + /* + * Clean up and we're done. + */ + viafb_mmio_write(VDMA_CSR0, VDMA_C_DONE); + viafb_mmio_write(VDMA_MR0, 0); /* Reset int enable */ + mutex_unlock(&viafb_dma_lock); + dma_free_coherent(&global_dev.pdev->dev, + nsg*sizeof(struct viafb_vx855_dma_descr), descrpages, + descr_handle); + return 0; +} +EXPORT_SYMBOL_GPL(viafb_dma_copy_out_sg); +#endif /* CONFIG_VIDEO_VIA_CAMERA */ + +/* ---------------------------------------------------------------------- */ +/* + * 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, VX900_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 VX900_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 via_pci_setup_mmio(struct viafb_dev *vdev) +{ + int ret; + /* + * Hook up to the device registers. Note that we soldier + * on if it fails; the framebuffer can operate (without + * acceleration) without this region. + */ + vdev->engine_start = pci_resource_start(vdev->pdev, 1); + vdev->engine_len = pci_resource_len(vdev->pdev, 1); + vdev->engine_mmio = ioremap(vdev->engine_start, + vdev->engine_len); + if (vdev->engine_mmio == NULL) + dev_err(&vdev->pdev->dev, + "Unable to map engine MMIO; operation will be " + "slow and crippled.\n"); + /* + * Map in framebuffer memory. For now, failure here is + * fatal. Unfortunately, in the absence of significant + * vmalloc space, failure here is also entirely plausible. + * Eventually we want to move away from mapping this + * entire region. + */ + if (vdev->chip_type == UNICHROME_VX900) + vdev->fbmem_start = pci_resource_start(vdev->pdev, 2); + else + vdev->fbmem_start = pci_resource_start(vdev->pdev, 0); + ret = vdev->fbmem_len = viafb_get_fb_size_from_pci(vdev->chip_type); + if (ret < 0) + goto out_unmap; + + /* try to map less memory on failure, 8 MB should be still enough */ + for (; vdev->fbmem_len >= 8 << 20; vdev->fbmem_len /= 2) { + vdev->fbmem = ioremap_wc(vdev->fbmem_start, vdev->fbmem_len); + if (vdev->fbmem) + break; + } + + if (vdev->fbmem == NULL) { + ret = -ENOMEM; + goto out_unmap; + } + return 0; +out_unmap: + iounmap(vdev->engine_mmio); + return ret; +} + +static void 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", + }, +#if IS_ENABLED(CONFIG_VIDEO_VIA_CAMERA) + { + .name = "viafb-camera", + }, +#endif +}; +#define N_SUBDEVS ARRAY_SIZE(viafb_subdevs) + +static int 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 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 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); + } +} + +/* + * Power management functions + */ +static __maybe_unused LIST_HEAD(viafb_pm_hooks); +static __maybe_unused DEFINE_MUTEX(viafb_pm_hooks_lock); + +void viafb_pm_register(struct viafb_pm_hooks *hooks) +{ + INIT_LIST_HEAD(&hooks->list); + + mutex_lock(&viafb_pm_hooks_lock); + list_add_tail(&hooks->list, &viafb_pm_hooks); + mutex_unlock(&viafb_pm_hooks_lock); +} +EXPORT_SYMBOL_GPL(viafb_pm_register); + +void viafb_pm_unregister(struct viafb_pm_hooks *hooks) +{ + mutex_lock(&viafb_pm_hooks_lock); + list_del(&hooks->list); + mutex_unlock(&viafb_pm_hooks_lock); +} +EXPORT_SYMBOL_GPL(viafb_pm_unregister); + +static int __maybe_unused via_suspend(struct device *dev) +{ + struct viafb_pm_hooks *hooks; + + /* + * "I've occasionally hit a few drivers that caused suspend + * failures, and each and every time it was a driver bug, and + * the right thing to do was to just ignore the error and suspend + * anyway - returning an error code and trying to undo the suspend + * is not what anybody ever really wants, even if our model + *_allows_ for it." + * -- Linus Torvalds, Dec. 7, 2009 + */ + mutex_lock(&viafb_pm_hooks_lock); + list_for_each_entry_reverse(hooks, &viafb_pm_hooks, list) + hooks->suspend(hooks->private); + mutex_unlock(&viafb_pm_hooks_lock); + + return 0; +} + +static int __maybe_unused via_resume(struct device *dev) +{ + struct viafb_pm_hooks *hooks; + + /* Now bring back any subdevs */ + mutex_lock(&viafb_pm_hooks_lock); + list_for_each_entry(hooks, &viafb_pm_hooks, list) + hooks->resume(hooks->private); + mutex_unlock(&viafb_pm_hooks_lock); + + return 0; +} + +static int via_pci_probe(struct pci_dev *pdev, const struct pci_device_id *ent) +{ + int ret; + + ret = aperture_remove_conflicting_pci_devices(pdev, "viafb"); + if (ret) + return 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; + if (machine_is_olpc()) + global_dev.port_cfg = olpc_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 device + */ + 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 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 const struct pci_device_id via_pci_table[] = { + { PCI_DEVICE(PCI_VENDOR_ID_VIA, UNICHROME_CLE266_DID), + .driver_data = UNICHROME_CLE266 }, + { 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_PM800_DID), + .driver_data = UNICHROME_PM800 }, + { PCI_DEVICE(PCI_VENDOR_ID_VIA, UNICHROME_CN700_DID), + .driver_data = UNICHROME_CN700 }, + { PCI_DEVICE(PCI_VENDOR_ID_VIA, UNICHROME_CX700_DID), + .driver_data = UNICHROME_CX700 }, + { PCI_DEVICE(PCI_VENDOR_ID_VIA, UNICHROME_CN750_DID), + .driver_data = UNICHROME_CN750 }, + { PCI_DEVICE(PCI_VENDOR_ID_VIA, UNICHROME_K8M890_DID), + .driver_data = UNICHROME_K8M890 }, + { PCI_DEVICE(PCI_VENDOR_ID_VIA, UNICHROME_P4M890_DID), + .driver_data = UNICHROME_P4M890 }, + { PCI_DEVICE(PCI_VENDOR_ID_VIA, UNICHROME_P4M900_DID), + .driver_data = UNICHROME_P4M900 }, + { 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 }, + { PCI_DEVICE(PCI_VENDOR_ID_VIA, UNICHROME_VX900_DID), + .driver_data = UNICHROME_VX900 }, + { } +}; +MODULE_DEVICE_TABLE(pci, via_pci_table); + +static const struct dev_pm_ops via_pm_ops = { +#ifdef CONFIG_PM_SLEEP + .suspend = via_suspend, + .resume = via_resume, + .freeze = NULL, + .thaw = via_resume, + .poweroff = NULL, + .restore = via_resume, +#endif +}; + +static struct pci_driver via_driver = { + .name = "viafb", + .id_table = via_pci_table, + .probe = via_pci_probe, + .remove = via_pci_remove, + .driver.pm = &via_pm_ops, +}; + +static int __init via_core_init(void) +{ + int ret; + + ret = viafb_init(); + if (ret) + return ret; + viafb_i2c_init(); + viafb_gpio_init(); + ret = pci_register_driver(&via_driver); + if (ret) { + viafb_gpio_exit(); + viafb_i2c_exit(); + return ret; + } + + return 0; +} + +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); |