drm/mgag200: Add vblank support
authorThomas Zimmermann <tzimmermann@suse.de>
Thu, 18 Jul 2024 10:44:16 +0000 (12:44 +0200)
committerThomas Zimmermann <tzimmermann@suse.de>
Fri, 19 Jul 2024 09:12:04 +0000 (11:12 +0200)
There's no VBLANK interrupt on Matrox chipsets. The workaround that is
being used here and in other free Matrox drivers is to program <linecomp>
to the value of <vblkstr> and enable the VLINE interrupt. This triggers
an interrupt at the time when VBLANK begins.

VLINE uses separate registers for enabling and clearing pending interrupts.
No extra synchronization between irq handler and the rest of the driver is
required.

v6:
- clear VLINE status bit before registering IRQ (Jocelyn)

v5:
- disable all interrupts before registering IRQ (Jocelyn)
- don't read from ICLEAR (Jocelyn)

v4:
- recreate patch on latest upstream
- use devm_request_irq() for managed cleanup
- fail if vblanking cannot be initialized
- rename register constants (Sam, Emil)
- clear interrupt before registering handler (Ville)
- move <linecomp> programming into separate commit
- set <linecomp> to <vblkstr>
- fix typo in commit message

v3:
- set <linecomp> to <vdisplay> + 1 to trigger at VBLANK
- expand comment on linecomp

v2:
- only signal vblank on CRTC 0
- use constants for registers and fields
- set VLINECLR before enabling interrupt
- test against STATUS and IEN in irq handler
- coding-style fixes

Signed-off-by: Thomas Zimmermann <tzimmermann@suse.de>
Reviewed-by: Jocelyn Falempe <jfalempe@redhat.com>
Acked-by: Gerd Hoffmann <kraxel@redhat.com>
Acked-by: Sam Ravnborg <sam@ravnborg.org>
Tested-by: Jocelyn Falempe <jfalempe@redhat.com>
Link: https://patchwork.freedesktop.org/patch/msgid/20240718104551.575912-7-tzimmermann@suse.de
12 files changed:
drivers/gpu/drm/mgag200/mgag200_drv.c
drivers/gpu/drm/mgag200/mgag200_drv.h
drivers/gpu/drm/mgag200/mgag200_g200.c
drivers/gpu/drm/mgag200/mgag200_g200eh.c
drivers/gpu/drm/mgag200/mgag200_g200eh3.c
drivers/gpu/drm/mgag200/mgag200_g200er.c
drivers/gpu/drm/mgag200/mgag200_g200ev.c
drivers/gpu/drm/mgag200/mgag200_g200ew3.c
drivers/gpu/drm/mgag200/mgag200_g200se.c
drivers/gpu/drm/mgag200/mgag200_g200wb.c
drivers/gpu/drm/mgag200/mgag200_mode.c
drivers/gpu/drm/mgag200/mgag200_reg.h

index 62080cf0f2da497f652f54089ec344acaa635c60..6623ee4e3277dfecefdc1fb94f0713aa50192e5c 100644 (file)
@@ -18,6 +18,7 @@
 #include <drm/drm_managed.h>
 #include <drm/drm_module.h>
 #include <drm/drm_pciids.h>
+#include <drm/drm_vblank.h>
 
 #include "mgag200_drv.h"
 
@@ -84,6 +85,34 @@ resource_size_t mgag200_probe_vram(void __iomem *mem, resource_size_t size)
        return offset - 65536;
 }
 
+static irqreturn_t mgag200_irq_handler(int irq, void *arg)
+{
+       struct drm_device *dev = arg;
+       struct mga_device *mdev = to_mga_device(dev);
+       struct drm_crtc *crtc;
+       u32 status, ien;
+
+       status = RREG32(MGAREG_STATUS);
+
+       if (status & MGAREG_STATUS_VLINEPEN) {
+               ien = RREG32(MGAREG_IEN);
+               if (!(ien & MGAREG_IEN_VLINEIEN))
+                       goto out;
+
+               crtc = drm_crtc_from_index(dev, 0);
+               if (WARN_ON_ONCE(!crtc))
+                       goto out;
+               drm_crtc_handle_vblank(crtc);
+
+               WREG32(MGAREG_ICLEAR, MGAREG_ICLEAR_VLINEICLR);
+
+               return IRQ_HANDLED;
+       }
+
+out:
+       return IRQ_NONE;
+}
+
 /*
  * DRM driver
  */
@@ -167,6 +196,7 @@ int mgag200_device_init(struct mga_device *mdev,
                        const struct mgag200_device_funcs *funcs)
 {
        struct drm_device *dev = &mdev->base;
+       struct pci_dev *pdev = to_pci_dev(dev->dev);
        u8 crtcext3, misc;
        int ret;
 
@@ -192,6 +222,16 @@ int mgag200_device_init(struct mga_device *mdev,
 
        mutex_unlock(&mdev->rmmio_lock);
 
+       WREG32(MGAREG_IEN, 0);
+       WREG32(MGAREG_ICLEAR, MGAREG_ICLEAR_VLINEICLR);
+
+       ret = devm_request_irq(&pdev->dev, pdev->irq, mgag200_irq_handler, IRQF_SHARED,
+                              dev->driver->name, dev);
+       if (ret) {
+               drm_err(dev, "Failed to acquire interrupt, error %d\n", ret);
+               return ret;
+       }
+
        return 0;
 }
 
index f97eaa49b089376fd0eb49de9e3b39eb847fd14f..829d32f50915681cce00cdbc9d673a99444e9496 100644 (file)
@@ -411,6 +411,8 @@ void mgag200_crtc_helper_atomic_disable(struct drm_crtc *crtc, struct drm_atomic
 void mgag200_crtc_reset(struct drm_crtc *crtc);
 struct drm_crtc_state *mgag200_crtc_atomic_duplicate_state(struct drm_crtc *crtc);
 void mgag200_crtc_atomic_destroy_state(struct drm_crtc *crtc, struct drm_crtc_state *crtc_state);
+int mgag200_crtc_enable_vblank(struct drm_crtc *crtc);
+void mgag200_crtc_disable_vblank(struct drm_crtc *crtc);
 
 #define MGAG200_CRTC_FUNCS \
        .reset = mgag200_crtc_reset, \
@@ -418,7 +420,9 @@ void mgag200_crtc_atomic_destroy_state(struct drm_crtc *crtc, struct drm_crtc_st
        .set_config = drm_atomic_helper_set_config, \
        .page_flip = drm_atomic_helper_page_flip, \
        .atomic_duplicate_state = mgag200_crtc_atomic_duplicate_state, \
-       .atomic_destroy_state = mgag200_crtc_atomic_destroy_state
+       .atomic_destroy_state = mgag200_crtc_atomic_destroy_state, \
+       .enable_vblank = mgag200_crtc_enable_vblank, \
+       .disable_vblank = mgag200_crtc_disable_vblank
 
 void mgag200_set_mode_regs(struct mga_device *mdev, const struct drm_display_mode *mode,
                           bool set_vidrst);
index f874e2949840994cb5f9a545e2e4ff4e749e3e35..77ce8d36cef05488985d337adcdb608329247774 100644 (file)
@@ -8,6 +8,7 @@
 #include <drm/drm_drv.h>
 #include <drm/drm_gem_atomic_helper.h>
 #include <drm/drm_probe_helper.h>
+#include <drm/drm_vblank.h>
 
 #include "mgag200_drv.h"
 
@@ -403,5 +404,9 @@ struct mga_device *mgag200_g200_device_create(struct pci_dev *pdev, const struct
        drm_mode_config_reset(dev);
        drm_kms_helper_poll_init(dev);
 
+       ret = drm_vblank_init(dev, 1);
+       if (ret)
+               return ERR_PTR(ret);
+
        return mdev;
 }
index 52bf49ead5c506de0ca1628d53a4f448516fab57..72bd8e3421c254bfdf4cdd910c94a8c43d1c83a5 100644 (file)
@@ -8,6 +8,7 @@
 #include <drm/drm_drv.h>
 #include <drm/drm_gem_atomic_helper.h>
 #include <drm/drm_probe_helper.h>
+#include <drm/drm_vblank.h>
 
 #include "mgag200_drv.h"
 
@@ -279,5 +280,9 @@ struct mga_device *mgag200_g200eh_device_create(struct pci_dev *pdev, const stru
        drm_mode_config_reset(dev);
        drm_kms_helper_poll_init(dev);
 
+       ret = drm_vblank_init(dev, 1);
+       if (ret)
+               return ERR_PTR(ret);
+
        return mdev;
 }
index e7f89b2a59fd05dc2a1f4c406fe862b585cecbde..1bbb0745b84a22139781888b534fa3a66a462e6d 100644 (file)
@@ -7,6 +7,7 @@
 #include <drm/drm_drv.h>
 #include <drm/drm_gem_atomic_helper.h>
 #include <drm/drm_probe_helper.h>
+#include <drm/drm_vblank.h>
 
 #include "mgag200_drv.h"
 
@@ -184,5 +185,9 @@ struct mga_device *mgag200_g200eh3_device_create(struct pci_dev *pdev,
        drm_mode_config_reset(dev);
        drm_kms_helper_poll_init(dev);
 
+       ret = drm_vblank_init(dev, 1);
+       if (ret)
+               return ERR_PTR(ret);
+
        return mdev;
 }
index 737a48aa9160013acfe8e275486c897cedaa2925..6d727ab1a7aa6bfa1bdfdb18e201105651c5dc2c 100644 (file)
@@ -8,6 +8,7 @@
 #include <drm/drm_drv.h>
 #include <drm/drm_gem_atomic_helper.h>
 #include <drm/drm_probe_helper.h>
+#include <drm/drm_vblank.h>
 
 #include "mgag200_drv.h"
 
@@ -315,5 +316,9 @@ struct mga_device *mgag200_g200er_device_create(struct pci_dev *pdev, const stru
        drm_mode_config_reset(dev);
        drm_kms_helper_poll_init(dev);
 
+       ret = drm_vblank_init(dev, 1);
+       if (ret)
+               return ERR_PTR(ret);
+
        return mdev;
 }
index 8d1ccc2ad94ae6b56f14342f0247d9aab2b2979b..e6c9ba61bf9782aafad5188228adca61eeb0f57c 100644 (file)
@@ -8,6 +8,7 @@
 #include <drm/drm_drv.h>
 #include <drm/drm_gem_atomic_helper.h>
 #include <drm/drm_probe_helper.h>
+#include <drm/drm_vblank.h>
 
 #include "mgag200_drv.h"
 
@@ -320,5 +321,9 @@ struct mga_device *mgag200_g200ev_device_create(struct pci_dev *pdev, const stru
        drm_mode_config_reset(dev);
        drm_kms_helper_poll_init(dev);
 
+       ret = drm_vblank_init(dev, 1);
+       if (ret)
+               return ERR_PTR(ret);
+
        return mdev;
 }
index 265f3e95830ae6db98bcb2c497ce7b5a3793fc2a..fbaa97c7e0daa0c3bb0b9f6e84b4062f2b9d9fbb 100644 (file)
@@ -7,6 +7,7 @@
 #include <drm/drm_drv.h>
 #include <drm/drm_gem_atomic_helper.h>
 #include <drm/drm_probe_helper.h>
+#include <drm/drm_vblank.h>
 
 #include "mgag200_drv.h"
 
@@ -202,5 +203,9 @@ struct mga_device *mgag200_g200ew3_device_create(struct pci_dev *pdev,
        drm_mode_config_reset(dev);
        drm_kms_helper_poll_init(dev);
 
+       ret = drm_vblank_init(dev, 1);
+       if (ret)
+               return ERR_PTR(ret);
+
        return mdev;
 }
index cf7f6897838f24f08fd0eb2afed78b80638a95a2..2a53ebf415393270a7c22cda35125935dbc36099 100644 (file)
@@ -8,6 +8,7 @@
 #include <drm/drm_drv.h>
 #include <drm/drm_gem_atomic_helper.h>
 #include <drm/drm_probe_helper.h>
+#include <drm/drm_vblank.h>
 
 #include "mgag200_drv.h"
 
@@ -520,5 +521,9 @@ struct mga_device *mgag200_g200se_device_create(struct pci_dev *pdev, const stru
        drm_mode_config_reset(dev);
        drm_kms_helper_poll_init(dev);
 
+       ret = drm_vblank_init(dev, 1);
+       if (ret)
+               return ERR_PTR(ret);
+
        return mdev;
 }
index e25477347c3ed0bbf3203afeb8db251620fff69a..33ef35c95acbb33f5a7a5f91a7564cf5b8eab7b1 100644 (file)
@@ -8,6 +8,7 @@
 #include <drm/drm_drv.h>
 #include <drm/drm_gem_atomic_helper.h>
 #include <drm/drm_probe_helper.h>
+#include <drm/drm_vblank.h>
 
 #include "mgag200_drv.h"
 
@@ -326,5 +327,9 @@ struct mga_device *mgag200_g200wb_device_create(struct pci_dev *pdev, const stru
        drm_mode_config_reset(dev);
        drm_kms_helper_poll_init(dev);
 
+       ret = drm_vblank_init(dev, 1);
+       if (ret)
+               return ERR_PTR(ret);
+
        return mdev;
 }
index ff90f29b0612b4e90bb0fa9af175de8e90eadf64..afabf693df64705cf8fbc7d7b9a0ac980473936b 100644 (file)
@@ -22,6 +22,7 @@
 #include <drm/drm_gem_framebuffer_helper.h>
 #include <drm/drm_panic.h>
 #include <drm/drm_print.h>
+#include <drm/drm_vblank.h>
 
 #include "mgag200_ddc.h"
 #include "mgag200_drv.h"
@@ -226,7 +227,14 @@ void mgag200_set_mode_regs(struct mga_device *mdev, const struct drm_display_mod
        vblkstr = mode->crtc_vblank_start;
        vblkend = vtotal + 1;
 
-       linecomp = vdispend;
+       /*
+        * There's no VBLANK interrupt on Matrox chipsets, so we use
+        * the VLINE interrupt instead. It triggers when the current
+        * <linecomp> has been reached. For VBLANK, this is the first
+        * non-visible line at the bottom of the screen. Therefore,
+        * keep <linecomp> in sync with <vblkstr>.
+        */
+       linecomp = vblkstr;
 
        misc = RREG8(MGA_MISC_IN);
 
@@ -640,6 +648,8 @@ void mgag200_crtc_helper_atomic_flush(struct drm_crtc *crtc, struct drm_atomic_s
        struct mgag200_crtc_state *mgag200_crtc_state = to_mgag200_crtc_state(crtc_state);
        struct drm_device *dev = crtc->dev;
        struct mga_device *mdev = to_mga_device(dev);
+       struct drm_pending_vblank_event *event;
+       unsigned long flags;
 
        if (crtc_state->enable && crtc_state->color_mgmt_changed) {
                const struct drm_format_info *format = mgag200_crtc_state->format;
@@ -649,6 +659,18 @@ void mgag200_crtc_helper_atomic_flush(struct drm_crtc *crtc, struct drm_atomic_s
                else
                        mgag200_crtc_set_gamma_linear(mdev, format);
        }
+
+       event = crtc->state->event;
+       if (event) {
+               crtc->state->event = NULL;
+
+               spin_lock_irqsave(&dev->event_lock, flags);
+               if (drm_crtc_vblank_get(crtc) != 0)
+                       drm_crtc_send_vblank_event(crtc, event);
+               else
+                       drm_crtc_arm_vblank_event(crtc, event);
+               spin_unlock_irqrestore(&dev->event_lock, flags);
+       }
 }
 
 void mgag200_crtc_helper_atomic_enable(struct drm_crtc *crtc, struct drm_atomic_state *old_state)
@@ -676,12 +698,16 @@ void mgag200_crtc_helper_atomic_enable(struct drm_crtc *crtc, struct drm_atomic_
 
        if (mdev->info->sync_bmc)
                mgag200_bmc_start_scanout(mdev);
+
+       drm_crtc_vblank_on(crtc);
 }
 
 void mgag200_crtc_helper_atomic_disable(struct drm_crtc *crtc, struct drm_atomic_state *old_state)
 {
        struct mga_device *mdev = to_mga_device(crtc->dev);
 
+       drm_crtc_vblank_off(crtc);
+
        if (mdev->info->sync_bmc)
                mgag200_bmc_stop_scanout(mdev);
 
@@ -732,6 +758,30 @@ void mgag200_crtc_atomic_destroy_state(struct drm_crtc *crtc, struct drm_crtc_st
        kfree(mgag200_crtc_state);
 }
 
+int mgag200_crtc_enable_vblank(struct drm_crtc *crtc)
+{
+       struct mga_device *mdev = to_mga_device(crtc->dev);
+       u32 ien;
+
+       WREG32(MGAREG_ICLEAR, MGAREG_ICLEAR_VLINEICLR);
+
+       ien = RREG32(MGAREG_IEN);
+       ien |= MGAREG_IEN_VLINEIEN;
+       WREG32(MGAREG_IEN, ien);
+
+       return 0;
+}
+
+void mgag200_crtc_disable_vblank(struct drm_crtc *crtc)
+{
+       struct mga_device *mdev = to_mga_device(crtc->dev);
+       u32 ien;
+
+       ien = RREG32(MGAREG_IEN);
+       ien &= ~(MGAREG_IEN_VLINEIEN);
+       WREG32(MGAREG_IEN, ien);
+}
+
 /*
  * Mode config
  */
index aa73463674e4bd2edd5f626cb30e3387a25f3649..d4fef8f25871f241611b8cf38bdf54422cd3906e 100644 (file)
 #define MGAREG_EXEC            0x0100
 
 #define        MGAREG_FIFOSTATUS       0x1e10
+
 #define        MGAREG_STATUS           0x1e14
+#define MGAREG_STATUS_VLINEPEN BIT(5)
+
 #define MGAREG_CACHEFLUSH       0x1fff
+
 #define        MGAREG_ICLEAR           0x1e18
+#define MGAREG_ICLEAR_VLINEICLR        BIT(5)
+
 #define        MGAREG_IEN              0x1e1c
+#define MGAREG_IEN_VLINEIEN    BIT(5)
 
 #define        MGAREG_VCOUNT           0x1e20