1// SPDX-License-Identifier: GPL-2.0-or-later
2/*
3 */
4
5#include <linux/module.h>
6#include <linux/pci.h>
7
8#include <drm/drm_drv.h>
9#include <drm/drm_atomic_helper.h>
10#include <drm/drm_managed.h>
11
12#include "bochs.h"
13
14static int bochs_modeset = -1;
15module_param_named(modeset, bochs_modeset, int, 0444);
16MODULE_PARM_DESC(modeset, "enable/disable kernel modesetting");
17
18/* ---------------------------------------------------------------------- */
19/* drm interface                                                          */
20
21static void bochs_unload(struct drm_device *dev)
22{
23	struct bochs_device *bochs = dev->dev_private;
24
25	bochs_mm_fini(bochs);
26}
27
28static int bochs_load(struct drm_device *dev)
29{
30	struct bochs_device *bochs;
31	int ret;
32
33	bochs = drmm_kzalloc(dev, sizeof(*bochs), GFP_KERNEL);
34	if (bochs == NULL)
35		return -ENOMEM;
36	dev->dev_private = bochs;
37	bochs->dev = dev;
38
39	ret = bochs_hw_init(dev);
40	if (ret)
41		goto err;
42
43	ret = bochs_mm_init(bochs);
44	if (ret)
45		goto err;
46
47	ret = bochs_kms_init(bochs);
48	if (ret)
49		goto err;
50
51	return 0;
52
53err:
54	bochs_unload(dev);
55	return ret;
56}
57
58DEFINE_DRM_GEM_FOPS(bochs_fops);
59
60static struct drm_driver bochs_driver = {
61	.driver_features	= DRIVER_GEM | DRIVER_MODESET | DRIVER_ATOMIC,
62	.fops			= &bochs_fops,
63	.name			= "bochs-drm",
64	.desc			= "bochs dispi vga interface (qemu stdvga)",
65	.date			= "20130925",
66	.major			= 1,
67	.minor			= 0,
68	DRM_GEM_VRAM_DRIVER,
69	.release                = bochs_unload,
70};
71
72/* ---------------------------------------------------------------------- */
73/* pm interface                                                           */
74
75#ifdef CONFIG_PM_SLEEP
76static int bochs_pm_suspend(struct device *dev)
77{
78	struct drm_device *drm_dev = dev_get_drvdata(dev);
79
80	return drm_mode_config_helper_suspend(drm_dev);
81}
82
83static int bochs_pm_resume(struct device *dev)
84{
85	struct drm_device *drm_dev = dev_get_drvdata(dev);
86
87	return drm_mode_config_helper_resume(drm_dev);
88}
89#endif
90
91static const struct dev_pm_ops bochs_pm_ops = {
92	SET_SYSTEM_SLEEP_PM_OPS(bochs_pm_suspend,
93				bochs_pm_resume)
94};
95
96/* ---------------------------------------------------------------------- */
97/* pci interface                                                          */
98
99static int bochs_pci_probe(struct pci_dev *pdev,
100			   const struct pci_device_id *ent)
101{
102	struct drm_device *dev;
103	unsigned long fbsize;
104	int ret;
105
106	fbsize = pci_resource_len(pdev, 0);
107	if (fbsize < 4 * 1024 * 1024) {
108		DRM_ERROR("less than 4 MB video memory, ignoring device\n");
109		return -ENOMEM;
110	}
111
112	ret = drm_fb_helper_remove_conflicting_pci_framebuffers(pdev, "bochsdrmfb");
113	if (ret)
114		return ret;
115
116	dev = drm_dev_alloc(&bochs_driver, &pdev->dev);
117	if (IS_ERR(dev))
118		return PTR_ERR(dev);
119
120	ret = pci_enable_device(pdev);
121	if (ret)
122		goto err_free_dev;
123
124	dev->pdev = pdev;
125	pci_set_drvdata(pdev, dev);
126
127	ret = bochs_load(dev);
128	if (ret)
129		goto err_free_dev;
130
131	ret = drm_dev_register(dev, 0);
132	if (ret)
133		goto err_unload;
134
135	drm_fbdev_generic_setup(dev, 32);
136	return ret;
137
138err_unload:
139	bochs_unload(dev);
140err_free_dev:
141	drm_dev_put(dev);
142	return ret;
143}
144
145static void bochs_pci_remove(struct pci_dev *pdev)
146{
147	struct drm_device *dev = pci_get_drvdata(pdev);
148
149	drm_dev_unplug(dev);
150	drm_atomic_helper_shutdown(dev);
151	bochs_hw_fini(dev);
152	drm_dev_put(dev);
153}
154
155static const struct pci_device_id bochs_pci_tbl[] = {
156	{
157		.vendor      = 0x1234,
158		.device      = 0x1111,
159		.subvendor   = PCI_SUBVENDOR_ID_REDHAT_QUMRANET,
160		.subdevice   = PCI_SUBDEVICE_ID_QEMU,
161		.driver_data = BOCHS_QEMU_STDVGA,
162	},
163	{
164		.vendor      = 0x1234,
165		.device      = 0x1111,
166		.subvendor   = PCI_ANY_ID,
167		.subdevice   = PCI_ANY_ID,
168		.driver_data = BOCHS_UNKNOWN,
169	},
170	{ /* end of list */ }
171};
172
173static struct pci_driver bochs_pci_driver = {
174	.name =		"bochs-drm",
175	.id_table =	bochs_pci_tbl,
176	.probe =	bochs_pci_probe,
177	.remove =	bochs_pci_remove,
178	.driver.pm =    &bochs_pm_ops,
179};
180
181/* ---------------------------------------------------------------------- */
182/* module init/exit                                                       */
183
184static int __init bochs_init(void)
185{
186	if (vgacon_text_force() && bochs_modeset == -1)
187		return -EINVAL;
188
189	if (bochs_modeset == 0)
190		return -EINVAL;
191
192	return pci_register_driver(&bochs_pci_driver);
193}
194
195static void __exit bochs_exit(void)
196{
197	pci_unregister_driver(&bochs_pci_driver);
198}
199
200module_init(bochs_init);
201module_exit(bochs_exit);
202
203MODULE_DEVICE_TABLE(pci, bochs_pci_tbl);
204MODULE_AUTHOR("Gerd Hoffmann <kraxel@redhat.com>");
205MODULE_LICENSE("GPL");
206