| // SPDX-License-Identifier: GPL-2.0-or-later |
| /* |
| */ |
| |
| #include <linux/module.h> |
| #include <linux/pci.h> |
| |
| #include <drm/drm_drv.h> |
| #include <drm/drm_atomic_helper.h> |
| #include <drm/drm_managed.h> |
| |
| #include "bochs.h" |
| |
| static int bochs_modeset = -1; |
| module_param_named(modeset, bochs_modeset, int, 0444); |
| MODULE_PARM_DESC(modeset, "enable/disable kernel modesetting"); |
| |
| /* ---------------------------------------------------------------------- */ |
| /* drm interface */ |
| |
| static void bochs_unload(struct drm_device *dev) |
| { |
| struct bochs_device *bochs = dev->dev_private; |
| |
| bochs_mm_fini(bochs); |
| } |
| |
| static int bochs_load(struct drm_device *dev) |
| { |
| struct bochs_device *bochs; |
| int ret; |
| |
| bochs = drmm_kzalloc(dev, sizeof(*bochs), GFP_KERNEL); |
| if (bochs == NULL) |
| return -ENOMEM; |
| dev->dev_private = bochs; |
| bochs->dev = dev; |
| |
| ret = bochs_hw_init(dev); |
| if (ret) |
| goto err; |
| |
| ret = bochs_mm_init(bochs); |
| if (ret) |
| goto err; |
| |
| ret = bochs_kms_init(bochs); |
| if (ret) |
| goto err; |
| |
| return 0; |
| |
| err: |
| bochs_unload(dev); |
| return ret; |
| } |
| |
| DEFINE_DRM_GEM_FOPS(bochs_fops); |
| |
| static struct drm_driver bochs_driver = { |
| .driver_features = DRIVER_GEM | DRIVER_MODESET | DRIVER_ATOMIC, |
| .fops = &bochs_fops, |
| .name = "bochs-drm", |
| .desc = "bochs dispi vga interface (qemu stdvga)", |
| .date = "20130925", |
| .major = 1, |
| .minor = 0, |
| DRM_GEM_VRAM_DRIVER, |
| .release = bochs_unload, |
| }; |
| |
| /* ---------------------------------------------------------------------- */ |
| /* pm interface */ |
| |
| #ifdef CONFIG_PM_SLEEP |
| static int bochs_pm_suspend(struct device *dev) |
| { |
| struct drm_device *drm_dev = dev_get_drvdata(dev); |
| |
| return drm_mode_config_helper_suspend(drm_dev); |
| } |
| |
| static int bochs_pm_resume(struct device *dev) |
| { |
| struct drm_device *drm_dev = dev_get_drvdata(dev); |
| |
| return drm_mode_config_helper_resume(drm_dev); |
| } |
| #endif |
| |
| static const struct dev_pm_ops bochs_pm_ops = { |
| SET_SYSTEM_SLEEP_PM_OPS(bochs_pm_suspend, |
| bochs_pm_resume) |
| }; |
| |
| /* ---------------------------------------------------------------------- */ |
| /* pci interface */ |
| |
| static int bochs_pci_probe(struct pci_dev *pdev, |
| const struct pci_device_id *ent) |
| { |
| struct drm_device *dev; |
| unsigned long fbsize; |
| int ret; |
| |
| fbsize = pci_resource_len(pdev, 0); |
| if (fbsize < 4 * 1024 * 1024) { |
| DRM_ERROR("less than 4 MB video memory, ignoring device\n"); |
| return -ENOMEM; |
| } |
| |
| ret = drm_fb_helper_remove_conflicting_pci_framebuffers(pdev, "bochsdrmfb"); |
| if (ret) |
| return ret; |
| |
| dev = drm_dev_alloc(&bochs_driver, &pdev->dev); |
| if (IS_ERR(dev)) |
| return PTR_ERR(dev); |
| |
| ret = pci_enable_device(pdev); |
| if (ret) |
| goto err_free_dev; |
| |
| dev->pdev = pdev; |
| pci_set_drvdata(pdev, dev); |
| |
| ret = bochs_load(dev); |
| if (ret) |
| goto err_free_dev; |
| |
| ret = drm_dev_register(dev, 0); |
| if (ret) |
| goto err_unload; |
| |
| drm_fbdev_generic_setup(dev, 32); |
| return ret; |
| |
| err_unload: |
| bochs_unload(dev); |
| err_free_dev: |
| drm_dev_put(dev); |
| return ret; |
| } |
| |
| static void bochs_pci_remove(struct pci_dev *pdev) |
| { |
| struct drm_device *dev = pci_get_drvdata(pdev); |
| |
| drm_dev_unplug(dev); |
| drm_atomic_helper_shutdown(dev); |
| bochs_hw_fini(dev); |
| drm_dev_put(dev); |
| } |
| |
| static const struct pci_device_id bochs_pci_tbl[] = { |
| { |
| .vendor = 0x1234, |
| .device = 0x1111, |
| .subvendor = PCI_SUBVENDOR_ID_REDHAT_QUMRANET, |
| .subdevice = PCI_SUBDEVICE_ID_QEMU, |
| .driver_data = BOCHS_QEMU_STDVGA, |
| }, |
| { |
| .vendor = 0x1234, |
| .device = 0x1111, |
| .subvendor = PCI_ANY_ID, |
| .subdevice = PCI_ANY_ID, |
| .driver_data = BOCHS_UNKNOWN, |
| }, |
| { /* end of list */ } |
| }; |
| |
| static struct pci_driver bochs_pci_driver = { |
| .name = "bochs-drm", |
| .id_table = bochs_pci_tbl, |
| .probe = bochs_pci_probe, |
| .remove = bochs_pci_remove, |
| .driver.pm = &bochs_pm_ops, |
| }; |
| |
| /* ---------------------------------------------------------------------- */ |
| /* module init/exit */ |
| |
| static int __init bochs_init(void) |
| { |
| if (vgacon_text_force() && bochs_modeset == -1) |
| return -EINVAL; |
| |
| if (bochs_modeset == 0) |
| return -EINVAL; |
| |
| return pci_register_driver(&bochs_pci_driver); |
| } |
| |
| static void __exit bochs_exit(void) |
| { |
| pci_unregister_driver(&bochs_pci_driver); |
| } |
| |
| module_init(bochs_init); |
| module_exit(bochs_exit); |
| |
| MODULE_DEVICE_TABLE(pci, bochs_pci_tbl); |
| MODULE_AUTHOR("Gerd Hoffmann <kraxel@redhat.com>"); |
| MODULE_LICENSE("GPL"); |