+
+static const struct drm_crtc_funcs glanda_crtc_funcs = {
+ .destroy = drm_crtc_cleanup,
+ .set_config = drm_atomic_helper_set_config,
+ .page_flip = drm_atomic_helper_page_flip,
+ .reset = drm_atomic_helper_crtc_reset,
+ .atomic_duplicate_state = drm_atomic_helper_crtc_duplicate_state,
+ .atomic_destroy_state = drm_atomic_helper_crtc_destroy_state,
+ .enable_vblank = glanda_crtc_enable_vblank,
+ .disable_vblank = glanda_crtc_disable_vblank,
+};
+
+static const struct drm_crtc_helper_funcs glanda_crtc_helper_funcs = {
+ .atomic_enable = drm_crtc_vblank_atomic_enable,
+ .atomic_disable = drm_crtc_vblank_atomic_disable,
+ .atomic_flush = glanda_crtc_atomic_flush,
+};
+
+static const struct drm_connector_helper_funcs glanda_connector_helper_funcs =
{
+ .get_modes = glanda_connector_get_modes,
+};
+
+static const struct drm_encoder_funcs glanda_encoder_funcs = {
+ .destroy = drm_encoder_cleanup,
+};
+
+static const struct drm_connector_funcs glanda_connector_funcs = {
+ .fill_modes = drm_helper_probe_single_connector_modes,
+ .destroy = drm_connector_cleanup,
+ .reset = drm_atomic_helper_connector_reset,
+ .atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state,
+ .atomic_destroy_state = drm_atomic_helper_connector_destroy_state,
+};
+
+static const struct drm_mode_config_funcs glanda_mode_config_funcs = {
+ .fb_create = drm_gem_fb_create_with_dirty,
+ .atomic_check = drm_atomic_helper_check,
+ .atomic_commit = drm_atomic_helper_commit,
+};
+
+DEFINE_DRM_GEM_FOPS(glanda_drm_fops);
+
+static const struct drm_driver glanda_drm_driver = {
+ .driver_features =
+ DRIVER_GEM | DRIVER_MODESET | DRIVER_ATOMIC,
+ .name = "glandagpu",
+ .desc = "GlandaGPU DRM Driver",
+ .major = 1,
+ .minor = 0,
+ .fops = &glanda_drm_fops,
+ .dumb_create = drm_gem_shmem_dumb_create,
+};
+
+static irqreturn_t glanda_irq_handler(int irq, void *dev_id)
+{
+ struct glanda_device *gdev = dev_id;
+ u32 isr, ier;
+
+ if (!gdev || !gdev->mmio_base)
+ return IRQ_NONE;
+
+ isr = readl(gdev->mmio_base + REG_ISR);
+ if (unlikely(isr == 0xFFFFFFFF))
+ return IRQ_NONE;
+
+ ier = readl(gdev->mmio_base + REG_IER);
+
+ if (!(isr & ier))
+ return IRQ_NONE;
+
+ if (isr & INT_VSYNC)
+ drm_crtc_handle_vblank(&gdev->crtc);
+
+ /* Clear interrupt(W1C) */
+ writel(isr, gdev->mmio_base + REG_ISR);
+ return IRQ_HANDLED;
+}
+
+/* Common DRM setup once MMIO/VRAM/IRQ are known(used by both probe paths) */
+static int glanda_drm_init(struct glanda_device *gdev, int irq)
+{
+ int ret;
+
+ writel(0, gdev->mmio_base + REG_IER);
+ writel(0xFFFFFFFF, gdev->mmio_base + REG_ISR); /* clear flags */
+
+ /* DRM mode config */
+ ret = drmm_mode_config_init(&gdev->drm);
+ if (ret)
+ return ret;
+
+ gdev->drm.mode_config.min_width = 640;
+ gdev->drm.mode_config.min_height = 480;
+ gdev->drm.mode_config.max_width = DRM_SHADOW_PLANE_MAX_WIDTH;
+ gdev->drm.mode_config.max_height = DRM_SHADOW_PLANE_MAX_HEIGHT;
+ gdev->drm.mode_config.funcs = &glanda_mode_config_funcs;
+
+ ret = drm_universal_plane_init(&gdev->drm, &gdev->primary_plane, 1 << 0,
+ &glanda_plane_funcs,
+ glanda_plane_formats,
+ ARRAY_SIZE(glanda_plane_formats), NULL,
+ DRM_PLANE_TYPE_PRIMARY, NULL);
+ if (ret) {
+ drm_err(&gdev->drm, "Failed to initialize primary plane\n");
+ return ret;
+ }
+ drm_plane_helper_add(&gdev->primary_plane, &glanda_plane_helper_funcs);
+
+ drm_plane_enable_fb_damage_clips(&gdev->primary_plane);
+
+ /* CRTC init */
+ ret = drm_crtc_init_with_planes(&gdev->drm, &gdev->crtc,
+ &gdev->primary_plane, NULL,
+ &glanda_crtc_funcs, NULL);
+ if (ret) {
+ drm_err(&gdev->drm, "Failed to initialize CRTC with planes\n");
+ return ret;
+ }
+ drm_crtc_helper_add(&gdev->crtc, &glanda_crtc_helper_funcs);
+
+ ret = drm_encoder_init(&gdev->drm, &gdev->encoder,
&glanda_encoder_funcs,
+ DRM_MODE_ENCODER_DAC, NULL);
+ if (ret) {
+ drm_err(&gdev->drm, "Failed to initialize encoder\n");
+ return ret;
+ }
+ gdev->encoder.possible_crtcs = 1;
+
+ ret = drm_connector_init(&gdev->drm, &gdev->connector,
+ &glanda_connector_funcs,
DRM_MODE_CONNECTOR_VGA);
+ if (ret) {
+ drm_err(&gdev->drm, "Failed to initialize connector\n");
+ return ret;
+ }
+ drm_connector_helper_add(&gdev->connector,
&glanda_connector_helper_funcs);
+
+ drm_connector_attach_encoder(&gdev->connector, &gdev->encoder);
+
+ /* VBlank init */
+ ret = drm_vblank_init(&gdev->drm, 1);
+ if (ret) {
+ drm_err(&gdev->drm, "Failed to initialize vblank\n");
+ return ret;
+ }
+
+ drm_mode_config_reset(&gdev->drm);
+
+ gdev->irq = irq;
+ ret = devm_request_irq(gdev->drm.dev, gdev->irq, glanda_irq_handler,
+ IRQF_SHARED, "glandagpu", gdev);
+ if (ret) {
+ drm_err(&gdev->drm, "Failed to request IRQ %d\n",
+ gdev->irq);
+ return ret;
+ }
+
+ ret = drm_dev_register(&gdev->drm, 0);
+ if (ret)
+ return ret;
+
+ return 0;
+}
+
+/* Shared teardown, mirrors glanda_drm_init() */
+static void glanda_drm_fini(struct glanda_device *gdev)
+{
+ drm_dev_unplug(&gdev->drm);
+ drm_atomic_helper_shutdown(&gdev->drm);
+}
+
+static int glandagpu_probe(struct platform_device *pdev)
+{
+ struct resource *res;
+ struct glanda_device *gdev;
+ int irq;
+
+ gdev = devm_drm_dev_alloc(&pdev->dev, &glanda_drm_driver, struct
glanda_device, drm);
+ if (IS_ERR(gdev))
+ return PTR_ERR(gdev);
+
+ platform_set_drvdata(pdev, gdev);
+
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ if (!res)
+ return -ENODEV;
+
+ if (resource_size(res) < GLANDA_MMIO_OFFSET + GLANDA_MMIO_SIZE) {
+ dev_err(&pdev->dev, "MMIO region too small: %llu bytes, need at
least %u\n",
+ (unsigned long long)resource_size(res),
+ GLANDA_MMIO_OFFSET + GLANDA_MMIO_SIZE);
+ return -EINVAL;
+ }
+
+ gdev->vram_phys = res->start;
+ gdev->vram_base = devm_ioremap_wc(&pdev->dev, res->start,
GLANDA_VRAM_SIZE);
+ gdev->mmio_base = devm_ioremap(&pdev->dev, res->start +
GLANDA_MMIO_OFFSET,
+ GLANDA_MMIO_SIZE);
+ if (!gdev->vram_base || !gdev->mmio_base) {
+ drm_err(&gdev->drm, "failed to ioremap\n");
+ return -ENOMEM;
+ }
+
+ irq = platform_get_irq(pdev, 0);
+ if (irq < 0)
+ return irq;
+
+ return glanda_drm_init(gdev, irq);
+}
+
+static void glandagpu_remove(struct platform_device *pdev)
+{
+ glanda_drm_fini(platform_get_drvdata(pdev));
+}
+
+static void glandagpu_shutdown(struct platform_device *pdev)
+{
+ struct glanda_device *gdev = platform_get_drvdata(pdev);
+
+ drm_atomic_helper_shutdown(&gdev->drm);
+}
+
+/* Device Tree match table. */
+static const struct of_device_id glanda_of_match[] = {
+ { .compatible = "kieweg,gpu-1.0" },
+ { }
+};
+
+MODULE_DEVICE_TABLE(of, glanda_of_match);
+
+static struct platform_driver glandagpu_driver = {
+ .driver = {
+ .name = "glandagpu",
+ .of_match_table = glanda_of_match,
+ },
+ .probe = glandagpu_probe,
+ .remove = glandagpu_remove,
+ .shutdown = glandagpu_shutdown,
+};
+
+/* PCI probe path for the QEMU test device, real hardware uses platform_driver
*/
+#ifdef CONFIG_PCI
+static int glandagpu_pci_probe(struct pci_dev *pdev, const struct
pci_device_id *id)
+{
+ struct glanda_device *gdev;
+ int ret;
+
+ ret = pcim_enable_device(pdev);
+ if (ret)
+ return ret;
+ pci_set_master(pdev);
+
+ if (pci_resource_len(pdev, 0) < GLANDA_MMIO_SIZE ||
+ pci_resource_len(pdev, 1) < GLANDA_VRAM_SIZE) {
+ dev_err(&pdev->dev, "BAR too small: BAR0=%llu (need %u), BAR1=%llu
(need %u)\n",
+ (unsigned long long)pci_resource_len(pdev, 0),
GLANDA_MMIO_SIZE,
+ (unsigned long long)pci_resource_len(pdev, 1),
GLANDA_VRAM_SIZE);
+ return -EINVAL;
+ }
+
+ ret = pcim_iomap_regions(pdev, BIT(0), "glandagpu");
+ if (ret)
+ return ret;
+
+ ret = pcim_request_region(pdev, 1, "glandagpu");
+ if (ret)
+ return ret;
+
+ gdev = devm_drm_dev_alloc(&pdev->dev, &glanda_drm_driver, struct
glanda_device, drm);
+ if (IS_ERR(gdev))
+ return PTR_ERR(gdev);
+
+ pci_set_drvdata(pdev, gdev);
+
+ gdev->mmio_base = pcim_iomap_table(pdev)[0];
+ gdev->vram_phys = pci_resource_start(pdev, 1);
+
+ gdev->vram_base = devm_ioremap_wc(&pdev->dev, gdev->vram_phys,
GLANDA_VRAM_SIZE);
+ if (!gdev->vram_base)
+ return -ENOMEM;
+
+ return glanda_drm_init(gdev, pdev->irq);
+}
+
+static void glandagpu_pci_remove(struct pci_dev *pdev)
+{
+ glanda_drm_fini(pci_get_drvdata(pdev));
+}
+
+static void glandagpu_pci_shutdown(struct pci_dev *pdev)
+{
+ struct glanda_device *gdev = pci_get_drvdata(pdev);
+
+ drm_atomic_helper_shutdown(&gdev->drm);
+}
+
+static const struct pci_device_id glanda_pci_ids[] = {
+ { PCI_DEVICE(PCI_VENDOR_ID_REDHAT_QUMRANET, PCI_DEVICE_ID_GLANDA_GPU) },
+ { }
+};
+
+MODULE_DEVICE_TABLE(pci, glanda_pci_ids);
+
+static struct pci_driver glandagpu_pci_driver = {
+ .name = "glandagpu-pci",
+ .id_table = glanda_pci_ids,
+ .probe = glandagpu_pci_probe,
+ .remove = glandagpu_pci_remove,
+ .shutdown = glandagpu_pci_shutdown,
+};
+#endif /* CONFIG_PCI */
+
+static int __init glandagpu_init(void)
+{
+ int ret;
+
+ ret = platform_driver_register(&glandagpu_driver);
+ if (ret) {
+ pr_err("GlandaGPU: Failed to register platform driver\n");
+ return ret;
+ }
+
+#ifdef CONFIG_PCI
+ ret = pci_register_driver(&glandagpu_pci_driver);
+ if (ret) {
+ pr_err("GlandaGPU: Failed to register PCI driver\n");
+ platform_driver_unregister(&glandagpu_driver);
+ return ret;
+ }
+#endif
+
+ return 0;
+}
+
+static void __exit glandagpu_exit(void)
+{
+#ifdef CONFIG_PCI
+ pci_unregister_driver(&glandagpu_pci_driver);
+#endif
+ platform_driver_unregister(&glandagpu_driver);
+}
+
+module_init(glandagpu_init);
+module_exit(glandagpu_exit);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Leander Kieweg <[email protected]>");
+MODULE_DESCRIPTION("DRM driver for GlandaGPU, an FPGA-based 2D GPU with VGA
output");