• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (c) 2018 Loongson Technology Co., Ltd.
3  * Authors:
4  *	Chen Zhu <zhuchen@loongson.cn>
5  *	Yaling Fang <fangyaling@loongson.cn>
6  *	Dandan Zhang <zhangdandan@loongson.cn>
7  *	Huacai Chen <chenhc@lemote.com>
8  * This program is free software; you can redistribute  it and/or modify it
9  * under  the terms of  the GNU General  Public License as published by the
10  * Free Software Foundation;  either version 2 of the  License, or (at your
11  * option) any later version.
12  */
13 
14 #include <asm/addrspace.h>
15 #include <linux/dma-mapping.h>
16 #include <linux/vmalloc.h>
17 #include <linux/console.h>
18 #include <linux/device.h>
19 #include <linux/slab.h>
20 #include <linux/fs.h>
21 #include <linux/pci.h>
22 #include <linux/pci_ids.h>
23 #include <linux/platform_device.h>
24 #include <linux/module.h>
25 #include <linux/kernel.h>
26 #include <linux/errno.h>
27 #include <linux/init.h>
28 #include <linux/string.h>
29 #include <linux/vga_switcheroo.h>
30 
31 #include <drm/drm_atomic_helper.h>
32 #include <drm/drm_crtc_helper.h>
33 #include <drm/drm_probe_helper.h>
34 #include <drm/drm_gem_cma_helper.h>
35 #include <drm/drm_gem_framebuffer_helper.h>
36 #include <loongson.h>
37 #include "loongson_drv.h"
38 
39 #define DEVICE_NAME	"loongson-drm"
40 #define DRIVER_NAME	"loongson-drm"
41 #define DRIVER_DESC	"Loongson DRM Driver"
42 #define DRIVER_DATE	"20201201"
43 #define DRIVER_MAJOR	1
44 #define DRIVER_MINOR	0
45 #define DRIVER_PATCHLEVEL	1
46 
47 bool hw_cursor = false;
48 module_param_named(cursor, hw_cursor, bool, 0600);
49 
50 bool poll_connector = false;
51 module_param_named(poll, poll_connector, bool, 0600);
52 
53 DEFINE_SPINLOCK(loongson_reglock);
54 
55 /**
56  * loongson_mode_funcs---basic driver provided mode setting functions
57  *
58  * Some global (i.e. not per-CRTC, connector, etc) mode setting functions that
59  * involve drivers.
60  */
61 static const struct drm_mode_config_funcs loongson_mode_funcs = {
62 	.fb_create = drm_gem_fb_create,
63 	.atomic_check = drm_atomic_helper_check,
64 	.atomic_commit = drm_atomic_helper_commit,
65 	.output_poll_changed = drm_fb_helper_output_poll_changed
66 };
67 
68 /**
69  *  loongson_drm_device_init  ----init drm device
70  *
71  * @dev   pointer to drm_device structure
72  * @flags start up flag
73  *
74  * RETURN
75  *   drm device init result
76  */
loongson_drm_device_init(struct drm_device * dev,uint32_t flags)77 static int loongson_drm_device_init(struct drm_device *dev, uint32_t flags)
78 {
79 	int ret = 0;
80 	struct loongson_drm_device *ldev = dev->dev_private;
81 
82 	ldev->num_crtc = 2;
83 	loongson_vbios_init(ldev);
84 
85 	/*BAR 0 contains registers */
86 	ldev->mmio_base = pci_resource_start(ldev->dev->pdev, 0);
87 	ldev->mmio_size = pci_resource_len(ldev->dev->pdev, 0);
88 
89 	ldev->mmio = pcim_iomap(dev->pdev, 0, 0);
90 	if (ldev->mmio == NULL)
91 		return -ENOMEM;
92 
93 	DRM_INFO("ldev->mmio_base = 0x%llx, ldev->mmio_size = 0x%llx\n",
94 			ldev->mmio_base, ldev->mmio_size);
95 
96 	if (!devm_request_mem_region(ldev->dev->dev, ldev->mmio_base, ldev->mmio_size,
97 			"loongson_drmfb_mmio")) {
98 		DRM_ERROR("Can't reserve mmio registers\n");
99 		return -ENOMEM;
100 	}
101 
102 	ret = loongson_gpio_init(ldev);
103 	if (ret < 0)
104 		DRM_ERROR("Failed to initialize dc gpios\n");
105 
106 	return ret;
107 }
108 
109 /**
110  * loongson_modeset_init --- init kernel mode setting
111  *
112  * @ldev: pointer to loongson_drm_device structure
113  *
114  * RETURN
115  *  return init result
116  */
loongson_modeset_init(struct loongson_drm_device * ldev)117 int loongson_modeset_init(struct loongson_drm_device *ldev)
118 {
119 	int i, ret;
120 	struct drm_encoder *encoder;
121 	struct drm_connector *connector;
122 
123 	ldev->mode_info[0].mode_config_initialized = true;
124 	ldev->mode_info[1].mode_config_initialized = true;
125 
126 	ldev->dev->mode_config.max_width = LOONGSON_MAX_FB_WIDTH;
127 	ldev->dev->mode_config.max_height = LOONGSON_MAX_FB_HEIGHT;
128 
129 	ldev->dev->mode_config.cursor_width = 32;
130 	ldev->dev->mode_config.cursor_height = 32;
131 
132 	ldev->dev->mode_config.allow_fb_modifiers = true;
133 
134 	ret = loongson_i2c_init(ldev);
135 	if (ret < 0) {
136 		DRM_ERROR("Failed to initialize i2c\n");
137 		return ret;
138 	}
139 
140 	loongson_crtc_init(ldev);
141 
142 	for (i=0; i<ldev->num_crtc; i++) {
143 		DRM_DEBUG("loongson drm encoder init\n");
144 		ldev->mode_info[i].crtc = &ldev->lcrtc[i];
145 		encoder = loongson_encoder_init(ldev->dev, i);
146 		if (!encoder) {
147 			DRM_ERROR("loongson_encoder_init failed\n");
148 			return -1;
149 		}
150 		ldev->mode_info[i].encoder = to_loongson_encoder(encoder);
151 
152 		DRM_DEBUG("loongson drm connector init\n");
153 		connector = loongson_connector_init(ldev->dev, i);
154 		if (!connector) {
155 			DRM_ERROR("loongson_connector_init failed\n");
156 			return -1;
157 		}
158 		ldev->mode_info[i].connector = to_loongson_connector(connector);
159 
160 		drm_connector_attach_encoder(connector, encoder);
161 		if (poll_connector)
162 			connector->polled = DRM_CONNECTOR_POLL_CONNECT | DRM_CONNECTOR_POLL_DISCONNECT;
163 	}
164 
165 	return 0;
166 }
167 
168 /**
169  * loongson_modeset_fini --- deinit kernel mode setting
170  *
171  * @ldev: pointer to loongson_drm_device structure
172  *
173  * RETURN
174  */
loongson_modeset_fini(struct loongson_drm_device * ldev)175 void loongson_modeset_fini(struct loongson_drm_device *ldev)
176 {
177 }
178 
loongson_detect_chip(struct loongson_drm_device * ldev)179 static int loongson_detect_chip(struct loongson_drm_device *ldev)
180 {
181 	struct pci_dev *pdev;
182 
183 	pdev = pci_get_device(PCI_VENDOR_ID_LOONGSON, PCI_DEVICE_ID_LOONGSON_DC1, NULL);
184 	if (pdev) {
185 		ldev->chip = dc_7a1000;
186 		ldev->gpu_pdev = pci_get_device(PCI_VENDOR_ID_LOONGSON, 0x7a15, NULL);
187 		DRM_INFO("Set LS7A1000 DC device\n");
188 		return 0;
189 	}
190 
191 	pdev = pci_get_device(PCI_VENDOR_ID_LOONGSON, PCI_DEVICE_ID_LOONGSON_DC2, NULL);
192 	if (pdev) {
193 		ldev->chip = dc_7a2000;
194 		ldev->gpu_pdev = pci_get_device(PCI_VENDOR_ID_LOONGSON, 0x7a25, NULL);
195 		DRM_INFO("Set LS7A2000 DC device\n");
196 		return 0;
197 	}
198 
199 	return -1;
200 }
201 
202 /**
203  * loongson_vga_load - setup chip and create an initial config
204  * @dev: DRM device
205  * @flags: startup flags
206  *
207  * The driver load routine has to do several things:
208  *   - initialize the memory manager
209  *   - allocate initial config memory
210  *   - setup the DRM framebuffer with the allocated memory
211  */
loongson_drm_load(struct drm_device * dev,unsigned long flags)212 static int loongson_drm_load(struct drm_device *dev, unsigned long flags)
213 {
214 	int r, ret, irq;
215 	struct loongson_drm_device *ldev;
216 
217 	dma_set_mask_and_coherent(dev->dev, DMA_BIT_MASK(32));
218 
219 	ldev = devm_kzalloc(dev->dev, sizeof(struct loongson_drm_device), GFP_KERNEL);
220 	if (ldev == NULL)
221 		return -ENOMEM;
222 	dev->dev_private = (void *)ldev;
223 	ldev->dev = dev;
224 
225 	ret = loongson_detect_chip(ldev);
226 	if (ret)
227 		dev_err(dev->dev, "Fatal error during detect chip: %d\n", ret);
228 
229 	ret = loongson_drm_device_init(dev, flags);
230 	DRM_DEBUG("end loongson drm device init.\n");
231 
232 	drm_mode_config_init(dev);
233 	dev->mode_config.funcs = (void *)&loongson_mode_funcs;
234 	dev->mode_config.preferred_depth = 24;
235 	dev->mode_config.prefer_shadow = 1;
236 
237 	irq = dev->pdev->irq;
238 	dev_set_drvdata(dev->dev, dev);
239 	pci_set_drvdata(dev->pdev, dev);
240 
241 	r = loongson_modeset_init(ldev);
242 	if (r)
243 		dev_err(dev->dev, "Fatal error during modeset init: %d\n", r);
244 
245 	r = drm_irq_install(dev, irq);
246 	if (r)
247 		dev_err(dev->dev, "Fatal error during irq install: %d\n", r);
248 
249 	ldev->inited = true;
250 	drm_mode_config_reset(dev);
251 
252 	r = drm_vblank_init(dev, ldev->num_crtc);
253 	if (r)
254 		dev_err(dev->dev, "Fatal error during vblank init: %d\n", r);
255 
256 	/* Make small buffers to store a hardware cursor (double buffered icon updates) */
257 	ldev->cursor = drm_gem_cma_create(dev, roundup(32*32*4, PAGE_SIZE));
258 
259 	drm_kms_helper_poll_init(dev);
260 
261 	drm_fb_helper_remove_conflicting_framebuffers(NULL, "loongson-drmfb", false);
262 
263 	return 0;
264 }
265 
266 /**
267  * loongson_drm_unload--release drm resource
268  *
269  * @dev: pointer to drm_device
270  *
271  */
loongson_drm_unload(struct drm_device * dev)272 static void loongson_drm_unload(struct drm_device *dev)
273 {
274         struct loongson_drm_device *ldev = dev->dev_private;
275 
276 	if (ldev == NULL)
277 		return;
278 
279 	loongson_modeset_fini(ldev);
280 	drm_mode_config_cleanup(dev);
281 	dev->dev_private = NULL;
282 	dev_set_drvdata(dev->dev, NULL);
283 	ldev->inited = false;
284 
285 	return;
286 }
287 
288 /**
289  * loongson_drm_open -Driver callback when a new struct drm_file is opened.
290  * Useful for setting up driver-private data structures like buffer allocators,
291  *  execution contexts or similar things.
292  *
293  * @dev DRM device
294  * @file DRM file private date
295  *
296  * RETURN
297  * 0 on success, a negative error code on failure, which will be promoted to
298  *  userspace as the result of the open() system call.
299  */
loongson_drm_open(struct drm_device * dev,struct drm_file * file)300 static int loongson_drm_open(struct drm_device *dev, struct drm_file *file)
301 {
302 	file->driver_priv = NULL;
303 
304 	DRM_DEBUG("open: dev=%p, file=%p", dev, file);
305 
306 	return 0;
307 }
308 
309 DEFINE_DRM_GEM_CMA_FOPS(fops);
310 
311 /**
312  * loongson_drm_driver - DRM device structure
313  *
314  * .load: driver callback to complete initialization steps after the driver is registered
315  * .unload:Reverse the effects of the driver load callback
316  * .open:Driver callback when a new struct drm_file is opened
317  * .fops:File operations for the DRM device node.
318  * .gem_free_object:deconstructor for drm_gem_objects
319  * .dumb_create:This creates a new dumb buffer in the driver’s backing storage manager
320  *  (GEM, TTM or something else entirely) and returns the resulting buffer handle.
321  *  This handle can then be wrapped up into a framebuffer modeset object
322  * .dumb_map_offset:Allocate an offset in the drm device node’s address space
323  *  to be able to memory map a dumb buffer
324  * .dump_destory:This destroys the userspace handle for the given dumb backing storage buffer
325  */
326 static struct drm_driver loongson_drm_driver = {
327 	.driver_features = DRIVER_MODESET | DRIVER_GEM | DRIVER_HAVE_IRQ | DRIVER_ATOMIC,
328 	.open = loongson_drm_open,
329 	.fops = &fops,
330 
331 	.dumb_create		= drm_gem_cma_dumb_create,
332 	.gem_free_object_unlocked = drm_gem_cma_free_object,
333 	.gem_vm_ops		= &drm_gem_cma_vm_ops,
334 
335 	.prime_handle_to_fd	= drm_gem_prime_handle_to_fd,
336 	.prime_fd_to_handle	= drm_gem_prime_fd_to_handle,
337 
338 	.gem_prime_import	= drm_gem_prime_import,
339 	.gem_prime_export	= drm_gem_prime_export,
340 
341 	.gem_prime_get_sg_table	= drm_gem_cma_prime_get_sg_table,
342 	.gem_prime_import_sg_table = drm_gem_cma_prime_import_sg_table,
343 	.gem_prime_vmap		= drm_gem_cma_prime_vmap,
344 	.gem_prime_vunmap	= drm_gem_cma_prime_vunmap,
345 	.gem_prime_mmap		= drm_gem_cma_prime_mmap,
346 
347 	.irq_handler = loongson_irq_handler,
348 	.irq_preinstall = loongson_irq_preinstall,
349 	.irq_postinstall = loongson_irq_postinstall,
350 	.irq_uninstall = loongson_irq_uninstall,
351 	.name = DRIVER_NAME,
352 	.desc = DRIVER_DESC,
353 	.date = DRIVER_DATE,
354 	.major = DRIVER_MAJOR,
355 	.minor = DRIVER_MINOR,
356 	.patchlevel = DRIVER_PATCHLEVEL,
357 };
358 
359 /**
360  * loongson_drm_pci_devices  -- pci device id info
361  *
362  * __u32 vendor, device           Vendor and device ID or PCI_ANY_ID
363  * __u32 subvendor, subdevice     Subsystem ID's or PCI_ANY_ID
364  * __u32 class, class_mask        (class,subclass,prog-if) triplet
365  * kernel_ulong_t driver_data     Data private to the driver
366  */
367 static struct pci_device_id loongson_drm_pci_devices[] = {
368 	{PCI_DEVICE(PCI_VENDOR_ID_LOONGSON, PCI_DEVICE_ID_LOONGSON_DC1)},
369 	{PCI_DEVICE(PCI_VENDOR_ID_LOONGSON, PCI_DEVICE_ID_LOONGSON_DC2)},
370 	{0, 0, 0, 0, 0, 0, 0}
371 };
372 
373 MODULE_DEVICE_TABLE(pci, loongson_drm_pci_devices);
374 
375 /**
376  * loongson_drm_pci_register -- add pci device
377  *
378  * @pdev PCI device
379  * @ent pci device id
380  */
loongson_drm_pci_register(struct pci_dev * pdev,const struct pci_device_id * ent)381 static int loongson_drm_pci_register(struct pci_dev *pdev,
382 				 const struct pci_device_id *ent)
383 
384 {
385 	int ret;
386 	struct drm_device *dev;
387 
388 	dev = drm_dev_alloc(&loongson_drm_driver, &pdev->dev);
389 	if (IS_ERR(dev))
390 		return PTR_ERR(dev);
391 
392 	ret = pci_enable_device(pdev);
393 	if (ret)
394 		goto err_free;
395 
396 	dev->pdev = pdev;
397 
398 	loongson_drm_load(dev, 0x0);
399 
400 	ret = drm_dev_register(dev, 0);
401 	if (ret)
402 		goto err_pdev;
403 
404 	drm_fbdev_generic_setup(dev, 32);
405 
406 	return 0;
407 
408 err_pdev:
409 	pci_disable_device(pdev);
410 err_free:
411 	drm_dev_put(dev);
412 	return ret;
413 }
414 
415 /**
416  * loongson_drm_pci_unregister -- release drm device
417  *
418  * @pdev PCI device
419  */
loongson_drm_pci_unregister(struct pci_dev * pdev)420 static void loongson_drm_pci_unregister(struct pci_dev *pdev)
421 {
422 	struct drm_device *dev = pci_get_drvdata(pdev);
423 	loongson_drm_unload(dev);
424 	drm_dev_put(dev);
425 }
426 
427 /*
428  * Suspend & resume.
429  */
430 /*
431  * loongson_drm_suspend - initiate device suspend
432  *
433  * @pdev: drm dev pointer
434  * @state: suspend state
435  *
436  * Puts the hw in the suspend state (all asics).
437  * Returns 0 for success or an error on failure.
438  * Called at driver suspend.
439  */
loongson_drm_suspend(struct drm_device * dev,bool suspend)440 int loongson_drm_suspend(struct drm_device *dev, bool suspend)
441 {
442         struct loongson_drm_device *ldev;
443 
444         if (dev == NULL || dev->dev_private == NULL)
445                 return -ENODEV;
446 
447         ldev = dev->dev_private;
448 
449         drm_kms_helper_poll_disable(dev);
450 	ldev->state = drm_atomic_helper_suspend(dev);
451 
452 	pci_save_state(dev->pdev);
453 	if (suspend) {
454 		/* Shut down the device */
455 		pci_disable_device(dev->pdev);
456 		pci_set_power_state(dev->pdev, PCI_D3hot);
457 	}
458 
459 	console_lock();
460 	drm_fb_helper_set_suspend(ldev->dev->fb_helper, 1);
461 	console_unlock();
462 
463 	return 0;
464 }
465 
466 /*
467  *  * loongson_drm_resume - initiate device suspend
468  *
469  * @pdev: drm dev pointer
470  * @state: suspend state
471  *
472  * Puts the hw in the suspend state (all asics).
473  * Returns 0 for success or an error on failure.
474  * Called at driver suspend.
475  */
476 
loongson_drm_resume(struct drm_device * dev,bool resume)477 int loongson_drm_resume(struct drm_device *dev, bool resume)
478 {
479 	struct loongson_drm_device *ldev = dev->dev_private;
480 
481 	console_lock();
482 
483 	if (resume) {
484 		pci_set_power_state(dev->pdev, PCI_D0);
485 		pci_restore_state(dev->pdev);
486 		if (pci_enable_device(dev->pdev)) {
487 			console_unlock();
488 			return -1;
489 		}
490 	}
491 
492         /* blat the mode back in */
493 	drm_atomic_helper_resume(dev, ldev->state);
494 
495 	drm_kms_helper_poll_enable(dev);
496 
497 	drm_fb_helper_set_suspend(ldev->dev->fb_helper, 0);
498 
499 	console_unlock();
500 
501 	return 0;
502 }
503 
504 /**
505  * loongson_drm_pm_suspend
506  *
507  * @dev   pointer to the device
508  *
509  * Executed before putting the system into a sleep state in which the
510  * contents of main memory are preserved.
511  */
loongson_drm_pm_suspend(struct device * dev)512 static int loongson_drm_pm_suspend(struct device *dev)
513 {
514 	struct drm_device *drm_dev = dev_get_drvdata(dev);
515 
516 	return loongson_drm_suspend(drm_dev, true);
517 }
518 
519 /**
520  * loongson_drm_pm_resume
521  *
522  * @dev pointer to the device
523  *
524  * Executed after waking the system up from a sleep state in which the
525  * contents of main memory were preserved.
526  */
loongson_drm_pm_resume(struct device * dev)527 static int loongson_drm_pm_resume(struct device *dev)
528 {
529 	struct drm_device *drm_dev = dev_get_drvdata(dev);
530 
531 	return loongson_drm_resume(drm_dev, true);
532 }
533 
534 /**
535  *  loongson_drm_pm_freeze
536  *
537  *  @dev pointer to device
538  *
539  *  Hibernation-specific, executed before creating a hibernation image.
540  *  Analogous to @suspend(), but it should not enable the device to signal
541  *  wakeup events or change its power state.
542  */
loongson_drm_pm_freeze(struct device * dev)543 static int loongson_drm_pm_freeze(struct device *dev)
544 {
545 	struct drm_device *drm_dev = dev_get_drvdata(dev);
546 
547 	return loongson_drm_suspend(drm_dev, false);
548 }
549 
550 /**
551  * loongson_drm_pm_draw
552  *
553  * @dev pointer to device
554  *
555  * Hibernation-specific, executed after creating a hibernation image OR
556  * if the creation of an image has failed.  Also executed after a failing
557  * attempt to restore the contents of main memory from such an image.
558  * Undo the changes made by the preceding @freeze(), so the device can be
559  * operated in the same way as immediately before the call to @freeze().
560  */
loongson_drm_pm_thaw(struct device * dev)561 static int loongson_drm_pm_thaw(struct device *dev)
562 {
563 	struct drm_device *drm_dev = dev_get_drvdata(dev);
564 
565 	return loongson_drm_resume(drm_dev, false);
566 }
567 
568 #define loongson_drm_pm_poweroff	loongson_drm_pm_freeze
569 #define loongson_drm_pm_restore		loongson_drm_pm_resume
570 
571 /*
572  * * struct dev_pm_ops - device PM callbacks
573  *
574  *@suspend:  Executed before putting the system into a sleep state in which the
575  *           contents of main memory are preserved.
576  *@resume:   Executed after waking the system up from a sleep state in which the
577  *           contents of main memory were preserved.
578  *@freeze:   Hibernation-specific, executed before creating a hibernation image.
579  *           Analogous to @suspend(), but it should not enable the device to signal
580  *           wakeup events or change its power state.  The majority of subsystems
581  *           (with the notable exception of the PCI bus type) expect the driver-level
582  *           @freeze() to save the device settings in memory to be used by @restore()
583  *           during the subsequent resume from hibernation.
584  *@thaw:     Hibernation-specific, executed after creating a hibernation image OR
585  *           if the creation of an image has failed.  Also executed after a failing
586  *           attempt to restore the contents of main memory from such an image.
587  *           Undo the changes made by the preceding @freeze(), so the device can be
588  *           operated in the same way as immediately before the call to @freeze().
589  *@poweroff: Hibernation-specific, executed after saving a hibernation image.
590  *           Analogous to @suspend(), but it need not save the device's settings in
591  *           memory.
592  *@restore:  Hibernation-specific, executed after restoring the contents of main
593  *           memory from a hibernation image, analogous to @resume().
594  */
595 static const struct dev_pm_ops loongson_drm_pm_ops = {
596 	.suspend = loongson_drm_pm_suspend,
597 	.resume = loongson_drm_pm_resume,
598 	.freeze = loongson_drm_pm_freeze,
599 	.thaw = loongson_drm_pm_thaw,
600 	.poweroff = loongson_drm_pm_poweroff,
601 	.restore = loongson_drm_pm_restore,
602 };
603 
604 /**
605  * loongson_drm_pci_driver -- pci driver structure
606  *
607  * .id_table : must be non-NULL for probe to be called
608  * .probe: New device inserted
609  * .remove: Device removed
610  * .resume: Device suspended
611  * .suspend: Device woken up
612  */
613 static struct pci_driver loongson_drm_pci_driver = {
614 	.name		= DRIVER_NAME,
615 	.id_table	= loongson_drm_pci_devices,
616 	.probe		= loongson_drm_pci_register,
617 	.remove		= loongson_drm_pci_unregister,
618 	.driver.pm	= &loongson_drm_pm_ops,
619 };
620 
621 /**
622  * loongson_drm_pci_init()  -- kernel module init function
623  */
loongson_drm_init(void)624 static int __init loongson_drm_init(void)
625 {
626 	int ret;
627 	struct pci_dev *pdev = NULL;
628 
629 	/* If external graphics card exist, use it as default */
630 	while ((pdev = pci_get_class(PCI_CLASS_DISPLAY_VGA << 8, pdev))) {
631 		if (pdev->vendor == PCI_VENDOR_ID_ATI)
632 			return 0;
633 		if (pdev->vendor == 0x1a03) /* ASpeed */
634 			return 0;
635 	}
636 
637 	ret = pci_register_driver(&loongson_drm_pci_driver);
638 
639 	return ret;
640 }
641 
642 /**
643  * loongson_drm_pci_exit()  -- kernel module exit function
644  */
loongson_drm_exit(void)645 static void __exit loongson_drm_exit(void)
646 {
647 	pci_unregister_driver(&loongson_drm_pci_driver);
648 }
649 
650 module_init(loongson_drm_init);
651 module_exit(loongson_drm_exit);
652 
653 MODULE_AUTHOR("Chen Zhu <zhuchen@loongson.cn>");
654 MODULE_AUTHOR("Huacai Chen <chenhuacai@loongson.cn>");
655 MODULE_DESCRIPTION("Loongson LS7A DRM Driver");
656 MODULE_LICENSE("GPL");
657