/* * 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/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> #include <asm/olpc.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 defined(CONFIG_VIDEO_VIA_CAMERA) || defined(CONFIG_VIDEO_VIA_CAMERA_MODULE) /* * 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); #if 0 /* * Copy a single buffer from FB memory, synchronously. This code works * but is not currently used. */ void viafb_dma_copy_out(unsigned int offset, dma_addr_t paddr, int len) { unsigned long flags; int csr; mutex_lock(&viafb_dma_lock); init_completion(&viafb_dma_completion); /* * Program the controller. */ spin_lock_irqsave(&global_dev.reg_lock, flags); viafb_mmio_write(VDMA_CSR0, VDMA_C_ENABLE|VDMA_C_DONE); /* Enable ints; must happen after CSR0 write! */ viafb_mmio_write(VDMA_MR0, VDMA_MR_TDIE); viafb_mmio_write(VDMA_MARL0, (int) (paddr & 0xfffffff0)); viafb_mmio_write(VDMA_MARH0, (int) ((paddr >> 28) & 0xfff)); /* Data sheet suggests DAR0 should be <<4, but it lies */ viafb_mmio_write(VDMA_DAR0, offset); viafb_mmio_write(VDMA_DQWCR0, len >> 4); viafb_mmio_write(VDMA_TMR0, 0); viafb_mmio_write(VDMA_DPRL0, 0); viafb_mmio_write(VDMA_DPRH0, 0); viafb_mmio_write(VDMA_PMR0, 0); csr = 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. */ wait_for_completion_interruptible(&viafb_dma_completion); viafb_mmio_write(VDMA_MR0, 0); /* Reset int enable */ mutex_unlock(&viafb_dma_lock); } EXPORT_SYMBOL_GPL(viafb_dma_copy_out); #endif /* * 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_nocache(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 defined(CONFIG_VIDEO_VIA_CAMERA) || defined(CONFIG_VIDEO_VIA_CAMERA_MODULE) { .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 */ #ifdef CONFIG_PM static LIST_HEAD(viafb_pm_hooks); static 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 via_suspend(struct pci_dev *pdev, pm_message_t state) { struct viafb_pm_hooks *hooks; if (state.event != PM_EVENT_SUSPEND) return 0; /* * "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); pci_save_state(pdev); pci_disable_device(pdev); pci_set_power_state(pdev, pci_choose_state(pdev, state)); return 0; } static int via_resume(struct pci_dev *pdev) { struct viafb_pm_hooks *hooks; /* Get the bus side powered up */ pci_set_power_state(pdev, PCI_D0); pci_restore_state(pdev); if (pci_enable_device(pdev)) return 0; pci_set_master(pdev); /* 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; } #endif /* CONFIG_PM */ static int 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; 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 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 struct pci_driver via_driver = { .name = "viafb", .id_table = via_pci_table, .probe = via_pci_probe, .remove = via_pci_remove, #ifdef CONFIG_PM .suspend = via_suspend, .resume = via_resume, #endif }; 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);