mirror of
https://github.com/torvalds/linux.git
synced 2026-04-21 08:13:56 -04:00
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
348 lines
8.1 KiB
C
348 lines
8.1 KiB
C
// SPDX-License-Identifier: GPL-2.0-only
|
|
/*
|
|
* Copyright 2012 Red Hat
|
|
*
|
|
* Authors: Matthew Garrett
|
|
* Dave Airlie
|
|
*/
|
|
|
|
#include <linux/module.h>
|
|
#include <linux/pci.h>
|
|
|
|
#include <drm/drm_aperture.h>
|
|
#include <drm/drm_atomic_helper.h>
|
|
#include <drm/drm_drv.h>
|
|
#include <drm/drm_fbdev_shmem.h>
|
|
#include <drm/drm_file.h>
|
|
#include <drm/drm_ioctl.h>
|
|
#include <drm/drm_managed.h>
|
|
#include <drm/drm_module.h>
|
|
#include <drm/drm_pciids.h>
|
|
#include <drm/drm_vblank.h>
|
|
|
|
#include "mgag200_drv.h"
|
|
|
|
static int mgag200_modeset = -1;
|
|
MODULE_PARM_DESC(modeset, "Disable/Enable modesetting");
|
|
module_param_named(modeset, mgag200_modeset, int, 0400);
|
|
|
|
int mgag200_init_pci_options(struct pci_dev *pdev, u32 option, u32 option2)
|
|
{
|
|
struct device *dev = &pdev->dev;
|
|
int err;
|
|
|
|
err = pci_write_config_dword(pdev, PCI_MGA_OPTION, option);
|
|
if (err != PCIBIOS_SUCCESSFUL) {
|
|
dev_err(dev, "pci_write_config_dword(PCI_MGA_OPTION) failed: %d\n", err);
|
|
return pcibios_err_to_errno(err);
|
|
}
|
|
|
|
err = pci_write_config_dword(pdev, PCI_MGA_OPTION2, option2);
|
|
if (err != PCIBIOS_SUCCESSFUL) {
|
|
dev_err(dev, "pci_write_config_dword(PCI_MGA_OPTION2) failed: %d\n", err);
|
|
return pcibios_err_to_errno(err);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
resource_size_t mgag200_probe_vram(void __iomem *mem, resource_size_t size)
|
|
{
|
|
int offset;
|
|
int orig;
|
|
int test1, test2;
|
|
int orig1, orig2;
|
|
size_t vram_size;
|
|
|
|
/* Probe */
|
|
orig = ioread16(mem);
|
|
iowrite16(0, mem);
|
|
|
|
vram_size = size;
|
|
|
|
for (offset = 0x100000; offset < vram_size; offset += 0x4000) {
|
|
orig1 = ioread8(mem + offset);
|
|
orig2 = ioread8(mem + offset + 0x100);
|
|
|
|
iowrite16(0xaa55, mem + offset);
|
|
iowrite16(0xaa55, mem + offset + 0x100);
|
|
|
|
test1 = ioread16(mem + offset);
|
|
test2 = ioread16(mem);
|
|
|
|
iowrite16(orig1, mem + offset);
|
|
iowrite16(orig2, mem + offset + 0x100);
|
|
|
|
if (test1 != 0xaa55)
|
|
break;
|
|
|
|
if (test2)
|
|
break;
|
|
}
|
|
|
|
iowrite16(orig, mem);
|
|
|
|
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
|
|
*/
|
|
|
|
DEFINE_DRM_GEM_FOPS(mgag200_driver_fops);
|
|
|
|
static const struct drm_driver mgag200_driver = {
|
|
.driver_features = DRIVER_ATOMIC | DRIVER_GEM | DRIVER_MODESET,
|
|
.fops = &mgag200_driver_fops,
|
|
.name = DRIVER_NAME,
|
|
.desc = DRIVER_DESC,
|
|
.date = DRIVER_DATE,
|
|
.major = DRIVER_MAJOR,
|
|
.minor = DRIVER_MINOR,
|
|
.patchlevel = DRIVER_PATCHLEVEL,
|
|
DRM_GEM_SHMEM_DRIVER_OPS,
|
|
};
|
|
|
|
/*
|
|
* DRM device
|
|
*/
|
|
|
|
resource_size_t mgag200_device_probe_vram(struct mga_device *mdev)
|
|
{
|
|
return mgag200_probe_vram(mdev->vram, resource_size(mdev->vram_res));
|
|
}
|
|
|
|
int mgag200_device_preinit(struct mga_device *mdev)
|
|
{
|
|
struct drm_device *dev = &mdev->base;
|
|
struct pci_dev *pdev = to_pci_dev(dev->dev);
|
|
resource_size_t start, len;
|
|
struct resource *res;
|
|
|
|
/* BAR 1 contains registers */
|
|
|
|
start = pci_resource_start(pdev, 1);
|
|
len = pci_resource_len(pdev, 1);
|
|
|
|
res = devm_request_mem_region(dev->dev, start, len, "mgadrmfb_mmio");
|
|
if (!res) {
|
|
drm_err(dev, "devm_request_mem_region(MMIO) failed\n");
|
|
return -ENXIO;
|
|
}
|
|
mdev->rmmio_res = res;
|
|
|
|
mdev->rmmio = pcim_iomap(pdev, 1, 0);
|
|
if (!mdev->rmmio)
|
|
return -ENOMEM;
|
|
|
|
/* BAR 0 is VRAM */
|
|
|
|
start = pci_resource_start(pdev, 0);
|
|
len = pci_resource_len(pdev, 0);
|
|
|
|
res = devm_request_mem_region(dev->dev, start, len, "mgadrmfb_vram");
|
|
if (!res) {
|
|
drm_err(dev, "devm_request_mem_region(VRAM) failed\n");
|
|
return -ENXIO;
|
|
}
|
|
mdev->vram_res = res;
|
|
|
|
#if defined(CONFIG_DRM_MGAG200_DISABLE_WRITECOMBINE)
|
|
mdev->vram = devm_ioremap(dev->dev, res->start, resource_size(res));
|
|
if (!mdev->vram)
|
|
return -ENOMEM;
|
|
#else
|
|
mdev->vram = devm_ioremap_wc(dev->dev, res->start, resource_size(res));
|
|
if (!mdev->vram)
|
|
return -ENOMEM;
|
|
|
|
/* Don't fail on errors, but performance might be reduced. */
|
|
devm_arch_phys_wc_add(dev->dev, res->start, resource_size(res));
|
|
#endif
|
|
|
|
return 0;
|
|
}
|
|
|
|
int mgag200_device_init(struct mga_device *mdev,
|
|
const struct mgag200_device_info *info,
|
|
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;
|
|
|
|
mdev->info = info;
|
|
mdev->funcs = funcs;
|
|
|
|
ret = drmm_mutex_init(dev, &mdev->rmmio_lock);
|
|
if (ret)
|
|
return ret;
|
|
|
|
mutex_lock(&mdev->rmmio_lock);
|
|
|
|
RREG_ECRT(0x03, crtcext3);
|
|
crtcext3 |= MGAREG_CRTCEXT3_MGAMODE;
|
|
WREG_ECRT(0x03, crtcext3);
|
|
|
|
WREG_ECRT(0x04, 0x00);
|
|
|
|
misc = RREG8(MGA_MISC_IN);
|
|
misc |= MGAREG_MISC_RAMMAPEN |
|
|
MGAREG_MISC_HIGH_PG_SEL;
|
|
WREG8(MGA_MISC_OUT, misc);
|
|
|
|
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;
|
|
}
|
|
|
|
/*
|
|
* PCI driver
|
|
*/
|
|
|
|
static const struct pci_device_id mgag200_pciidlist[] = {
|
|
{ PCI_VENDOR_ID_MATROX, 0x520, PCI_ANY_ID, PCI_ANY_ID, 0, 0, G200_PCI },
|
|
{ PCI_VENDOR_ID_MATROX, 0x521, PCI_ANY_ID, PCI_ANY_ID, 0, 0, G200_AGP },
|
|
{ PCI_VENDOR_ID_MATROX, 0x522, PCI_ANY_ID, PCI_ANY_ID, 0, 0, G200_SE_A },
|
|
{ PCI_VENDOR_ID_MATROX, 0x524, PCI_ANY_ID, PCI_ANY_ID, 0, 0, G200_SE_B },
|
|
{ PCI_VENDOR_ID_MATROX, 0x530, PCI_ANY_ID, PCI_ANY_ID, 0, 0, G200_EV },
|
|
{ PCI_VENDOR_ID_MATROX, 0x532, PCI_ANY_ID, PCI_ANY_ID, 0, 0, G200_WB },
|
|
{ PCI_VENDOR_ID_MATROX, 0x533, PCI_ANY_ID, PCI_ANY_ID, 0, 0, G200_EH },
|
|
{ PCI_VENDOR_ID_MATROX, 0x534, PCI_ANY_ID, PCI_ANY_ID, 0, 0, G200_ER },
|
|
{ PCI_VENDOR_ID_MATROX, 0x536, PCI_ANY_ID, PCI_ANY_ID, 0, 0, G200_EW3 },
|
|
{ PCI_VENDOR_ID_MATROX, 0x538, PCI_ANY_ID, PCI_ANY_ID, 0, 0, G200_EH3 },
|
|
{0,}
|
|
};
|
|
|
|
MODULE_DEVICE_TABLE(pci, mgag200_pciidlist);
|
|
|
|
static int
|
|
mgag200_pci_probe(struct pci_dev *pdev, const struct pci_device_id *ent)
|
|
{
|
|
enum mga_type type = (enum mga_type)ent->driver_data;
|
|
struct mga_device *mdev;
|
|
struct drm_device *dev;
|
|
int ret;
|
|
|
|
ret = drm_aperture_remove_conflicting_pci_framebuffers(pdev, &mgag200_driver);
|
|
if (ret)
|
|
return ret;
|
|
|
|
ret = pcim_enable_device(pdev);
|
|
if (ret)
|
|
return ret;
|
|
|
|
switch (type) {
|
|
case G200_PCI:
|
|
case G200_AGP:
|
|
mdev = mgag200_g200_device_create(pdev, &mgag200_driver);
|
|
break;
|
|
case G200_SE_A:
|
|
case G200_SE_B:
|
|
mdev = mgag200_g200se_device_create(pdev, &mgag200_driver, type);
|
|
break;
|
|
case G200_WB:
|
|
mdev = mgag200_g200wb_device_create(pdev, &mgag200_driver);
|
|
break;
|
|
case G200_EV:
|
|
mdev = mgag200_g200ev_device_create(pdev, &mgag200_driver);
|
|
break;
|
|
case G200_EH:
|
|
mdev = mgag200_g200eh_device_create(pdev, &mgag200_driver);
|
|
break;
|
|
case G200_EH3:
|
|
mdev = mgag200_g200eh3_device_create(pdev, &mgag200_driver);
|
|
break;
|
|
case G200_ER:
|
|
mdev = mgag200_g200er_device_create(pdev, &mgag200_driver);
|
|
break;
|
|
case G200_EW3:
|
|
mdev = mgag200_g200ew3_device_create(pdev, &mgag200_driver);
|
|
break;
|
|
default:
|
|
dev_err(&pdev->dev, "Device type %d is unsupported\n", type);
|
|
return -ENODEV;
|
|
}
|
|
if (IS_ERR(mdev))
|
|
return PTR_ERR(mdev);
|
|
dev = &mdev->base;
|
|
|
|
ret = drm_dev_register(dev, 0);
|
|
if (ret)
|
|
return ret;
|
|
|
|
/*
|
|
* FIXME: A 24-bit color depth does not work with 24 bpp on
|
|
* G200ER. Force 32 bpp.
|
|
*/
|
|
drm_fbdev_shmem_setup(dev, 32);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void mgag200_pci_remove(struct pci_dev *pdev)
|
|
{
|
|
struct drm_device *dev = pci_get_drvdata(pdev);
|
|
|
|
drm_dev_unregister(dev);
|
|
drm_atomic_helper_shutdown(dev);
|
|
}
|
|
|
|
static void mgag200_pci_shutdown(struct pci_dev *pdev)
|
|
{
|
|
drm_atomic_helper_shutdown(pci_get_drvdata(pdev));
|
|
}
|
|
|
|
static struct pci_driver mgag200_pci_driver = {
|
|
.name = DRIVER_NAME,
|
|
.id_table = mgag200_pciidlist,
|
|
.probe = mgag200_pci_probe,
|
|
.remove = mgag200_pci_remove,
|
|
.shutdown = mgag200_pci_shutdown,
|
|
};
|
|
|
|
drm_module_pci_driver_if_modeset(mgag200_pci_driver, mgag200_modeset);
|
|
|
|
MODULE_AUTHOR(DRIVER_AUTHOR);
|
|
MODULE_DESCRIPTION(DRIVER_DESC);
|
|
MODULE_LICENSE("GPL");
|