mirror of
https://github.com/torvalds/linux.git
synced 2026-04-24 01:25:49 -04:00
This reverts commitd930ffa5d6. According to Thomas, commitd930ffa5d6("drm/gma500: use drm_crtc_vblank_crtc()") breaks the driver with a NULL-ptr oops on startup. This is because the IRQ initialization in gma_irq_install() now uses CRTCs that are only allocated later in psb_modeset_init(). Stack trace is below. Revert. Go back to the drawing board. [ 65.831766] Oops: general protection fault, probably for non-canonical address 0xdffffc0000000021: 0000 [#1] SMP KASAN NOPTI [ 65.832114] KASAN: null-ptr-deref in range [0x0000000000000108-0x000000000000010f] [ 65.832232] CPU: 1 UID: 0 PID: 296 Comm: (udev-worker) Tainted: G E 6.19.0-rc6-1-default+ #4622 PREEMPT(voluntary) [ 65.832376] Tainted: [E]=UNSIGNED_MODULE [ 65.832448] Hardware name: /DN2800MT, BIOS MTCDT10N.86A.0164.2012.1213.1024 12/13/2012 [ 65.832543] RIP: 0010:drm_crtc_vblank_crtc+0x24/0xd0 [ 65.832652] Code: 90 90 90 90 90 90 0f 1f 44 00 00 48 89 f8 48 81 c7 18 01 00 00 48 83 ec 10 48 ba 00 00 00 00 00 fc ff df 48 89 f9 48 c1 e9 03 <0f> b6 14 11 84 d2 74 05 80 fa 03 7e 58 48 89 c6 8b 90 18 01 00 00 [ 65.832820] RSP: 0018:ffff88800c8f7688 EFLAGS: 00010006 [ 65.832919] RAX: fffffffffffffff0 RBX: ffff88800fff4928 RCX: 0000000000000021 [ 65.833011] RDX: dffffc0000000000 RSI: ffffc90000978130 RDI: 0000000000000108 [ 65.833107] RBP: ffffed1001ffea03 R08: 0000000000000000 R09: ffffed100191eec7 [ 65.833199] R10: 0000000000000001 R11: 0000000000000001 R12: ffff8880014480c8 [ 65.833289] R13: dffffc0000000000 R14: fffffffffffffff0 R15: ffff88800fff4000 [ 65.833380] FS: 00007fe53d4d5d80(0000) GS:ffff888148dd8000(0000) knlGS:0000000000000000 [ 65.833488] CS: 0010 DS: 0000 ES: 0000 CR0: 0000000080050033 [ 65.833575] CR2: 00007fac707420b8 CR3: 000000000ebd1000 CR4: 00000000000006f0 [ 65.833668] Call Trace: [ 65.833735] <TASK> [ 65.833808] gma_irq_preinstall+0x190/0x3e0 [gma500_gfx] [ 65.834054] gma_irq_install+0xb2/0x240 [gma500_gfx] [ 65.834282] psb_driver_load+0x7b2/0x1090 [gma500_gfx] [ 65.834516] ? __pfx_psb_driver_load+0x10/0x10 [gma500_gfx] [ 65.834726] ? ksize+0x1d/0x40 [ 65.834817] ? drmm_add_final_kfree+0x3b/0xb0 [ 65.834935] ? __pfx_psb_pci_probe+0x10/0x10 [gma500_gfx] [ 65.835164] psb_pci_probe+0xc8/0x150 [gma500_gfx] [ 65.835384] local_pci_probe+0xd5/0x190 [ 65.835492] pci_call_probe+0x167/0x4b0 [ 65.835594] ? __pfx_pci_call_probe+0x10/0x10 [ 65.835693] ? local_clock+0x11/0x30 [ 65.835808] ? __pfx___driver_attach+0x10/0x10 [ 65.835915] ? do_raw_spin_unlock+0x55/0x230 [ 65.836014] ? pci_match_device+0x303/0x790 [ 65.836124] ? pci_match_device+0x386/0x790 [ 65.836226] ? __pfx_pci_assign_irq+0x10/0x10 [ 65.836320] ? kernfs_create_link+0x16a/0x230 [ 65.836418] ? do_raw_spin_unlock+0x55/0x230 [ 65.836526] ? __pfx___driver_attach+0x10/0x10 [ 65.836626] pci_device_probe+0x175/0x2c0 [ 65.836735] call_driver_probe+0x64/0x1e0 [ 65.836842] really_probe+0x194/0x740 [ 65.836951] ? __pfx___driver_attach+0x10/0x10 [ 65.837053] __driver_probe_device+0x18c/0x3a0 [ 65.837163] ? __pfx___driver_attach+0x10/0x10 [ 65.837262] driver_probe_device+0x4a/0x120 [ 65.837369] __driver_attach+0x19c/0x550 [ 65.837474] ? __pfx___driver_attach+0x10/0x10 [ 65.837575] bus_for_each_dev+0xe6/0x150 [ 65.837669] ? local_clock+0x11/0x30 [ 65.837770] ? __pfx_bus_for_each_dev+0x10/0x10 [ 65.837891] bus_add_driver+0x2af/0x4f0 [ 65.838000] ? __pfx_psb_init+0x10/0x10 [gma500_gfx] [ 65.838236] driver_register+0x19f/0x3a0 [ 65.838342] ? rcu_is_watching+0x11/0xb0 [ 65.838446] do_one_initcall+0xb5/0x3a0 [ 65.838546] ? __pfx_do_one_initcall+0x10/0x10 [ 65.838644] ? __kasan_slab_alloc+0x2c/0x70 [ 65.838741] ? rcu_is_watching+0x11/0xb0 [ 65.838837] ? __kmalloc_cache_noprof+0x3e8/0x6e0 [ 65.838937] ? klp_module_coming+0x1a0/0x2e0 [ 65.839033] ? do_init_module+0x85/0x7f0 [ 65.839126] ? kasan_unpoison+0x40/0x70 [ 65.839230] do_init_module+0x26e/0x7f0 [ 65.839341] ? __pfx_do_init_module+0x10/0x10 [ 65.839450] init_module_from_file+0x13f/0x160 [ 65.839549] ? __pfx_init_module_from_file+0x10/0x10 [ 65.839651] ? __lock_acquire+0x578/0xae0 [ 65.839791] ? do_raw_spin_unlock+0x55/0x230 [ 65.839886] ? idempotent_init_module+0x585/0x720 [ 65.839993] idempotent_init_module+0x1ff/0x720 [ 65.840097] ? __pfx_cred_has_capability.isra.0+0x10/0x10 [ 65.840211] ? __pfx_idempotent_init_module+0x10/0x10 Reported-by: Thomas Zimmermann <tzimmermann@suse.de> Closes: https://lore.kernel.org/r/5aec1964-072c-4335-8f37-35e6efb4910e@suse.de Fixes:d930ffa5d6("drm/gma500: use drm_crtc_vblank_crtc()") Cc: Patrik Jakobsson <patrik.r.jakobsson@gmail.com> Signed-off-by: Jani Nikula <jani.nikula@intel.com> Reviewed-by: Thomas Zimmermann <tzimmermann@suse.de> Tested-by: Thomas Zimmermann <tzimmermann@suse.de> Signed-off-by: Patrik Jakobsson <patrik.r.jakobsson@gmail.com> Link: https://patch.msgid.link/20260130151319.31264-1-jani.nikula@intel.com
495 lines
13 KiB
C
495 lines
13 KiB
C
// SPDX-License-Identifier: GPL-2.0-only
|
|
/**************************************************************************
|
|
* Copyright (c) 2007, Intel Corporation.
|
|
* All Rights Reserved.
|
|
*
|
|
* Intel funded Tungsten Graphics (http://www.tungstengraphics.com) to
|
|
* develop this driver.
|
|
*
|
|
**************************************************************************/
|
|
|
|
#include <drm/drm_drv.h>
|
|
#include <drm/drm_print.h>
|
|
#include <drm/drm_vblank.h>
|
|
|
|
#include "power.h"
|
|
#include "psb_drv.h"
|
|
#include "psb_intel_reg.h"
|
|
#include "psb_irq.h"
|
|
#include "psb_reg.h"
|
|
|
|
/*
|
|
* inline functions
|
|
*/
|
|
|
|
static inline u32 gma_pipestat(int pipe)
|
|
{
|
|
if (pipe == 0)
|
|
return PIPEASTAT;
|
|
if (pipe == 1)
|
|
return PIPEBSTAT;
|
|
if (pipe == 2)
|
|
return PIPECSTAT;
|
|
BUG();
|
|
}
|
|
|
|
static inline u32 gma_pipeconf(int pipe)
|
|
{
|
|
if (pipe == 0)
|
|
return PIPEACONF;
|
|
if (pipe == 1)
|
|
return PIPEBCONF;
|
|
if (pipe == 2)
|
|
return PIPECCONF;
|
|
BUG();
|
|
}
|
|
|
|
void gma_enable_pipestat(struct drm_psb_private *dev_priv, int pipe, u32 mask)
|
|
{
|
|
if ((dev_priv->pipestat[pipe] & mask) != mask) {
|
|
u32 reg = gma_pipestat(pipe);
|
|
dev_priv->pipestat[pipe] |= mask;
|
|
/* Enable the interrupt, clear any pending status */
|
|
if (gma_power_begin(&dev_priv->dev, false)) {
|
|
u32 writeVal = PSB_RVDC32(reg);
|
|
writeVal |= (mask | (mask >> 16));
|
|
PSB_WVDC32(writeVal, reg);
|
|
(void) PSB_RVDC32(reg);
|
|
gma_power_end(&dev_priv->dev);
|
|
}
|
|
}
|
|
}
|
|
|
|
void gma_disable_pipestat(struct drm_psb_private *dev_priv, int pipe, u32 mask)
|
|
{
|
|
if ((dev_priv->pipestat[pipe] & mask) != 0) {
|
|
u32 reg = gma_pipestat(pipe);
|
|
dev_priv->pipestat[pipe] &= ~mask;
|
|
if (gma_power_begin(&dev_priv->dev, false)) {
|
|
u32 writeVal = PSB_RVDC32(reg);
|
|
writeVal &= ~mask;
|
|
PSB_WVDC32(writeVal, reg);
|
|
(void) PSB_RVDC32(reg);
|
|
gma_power_end(&dev_priv->dev);
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Display controller interrupt handler for pipe event.
|
|
*/
|
|
static void gma_pipe_event_handler(struct drm_device *dev, int pipe)
|
|
{
|
|
struct drm_psb_private *dev_priv = to_drm_psb_private(dev);
|
|
|
|
uint32_t pipe_stat_val = 0;
|
|
uint32_t pipe_stat_reg = gma_pipestat(pipe);
|
|
uint32_t pipe_enable = dev_priv->pipestat[pipe];
|
|
uint32_t pipe_status = dev_priv->pipestat[pipe] >> 16;
|
|
uint32_t pipe_clear;
|
|
uint32_t i = 0;
|
|
|
|
spin_lock(&dev_priv->irqmask_lock);
|
|
|
|
pipe_stat_val = PSB_RVDC32(pipe_stat_reg);
|
|
pipe_stat_val &= pipe_enable | pipe_status;
|
|
pipe_stat_val &= pipe_stat_val >> 16;
|
|
|
|
spin_unlock(&dev_priv->irqmask_lock);
|
|
|
|
/* Clear the 2nd level interrupt status bits
|
|
* Sometimes the bits are very sticky so we repeat until they unstick */
|
|
for (i = 0; i < 0xffff; i++) {
|
|
PSB_WVDC32(PSB_RVDC32(pipe_stat_reg), pipe_stat_reg);
|
|
pipe_clear = PSB_RVDC32(pipe_stat_reg) & pipe_status;
|
|
|
|
if (pipe_clear == 0)
|
|
break;
|
|
}
|
|
|
|
if (pipe_clear)
|
|
dev_err(dev->dev,
|
|
"%s, can't clear status bits for pipe %d, its value = 0x%x.\n",
|
|
__func__, pipe, PSB_RVDC32(pipe_stat_reg));
|
|
|
|
if (pipe_stat_val & PIPE_VBLANK_STATUS) {
|
|
struct drm_crtc *crtc = drm_crtc_from_index(dev, pipe);
|
|
struct gma_crtc *gma_crtc = to_gma_crtc(crtc);
|
|
unsigned long flags;
|
|
|
|
drm_handle_vblank(dev, pipe);
|
|
|
|
spin_lock_irqsave(&dev->event_lock, flags);
|
|
if (gma_crtc->page_flip_event) {
|
|
drm_crtc_send_vblank_event(crtc,
|
|
gma_crtc->page_flip_event);
|
|
gma_crtc->page_flip_event = NULL;
|
|
drm_crtc_vblank_put(crtc);
|
|
}
|
|
spin_unlock_irqrestore(&dev->event_lock, flags);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Display controller interrupt handler.
|
|
*/
|
|
static void gma_vdc_interrupt(struct drm_device *dev, uint32_t vdc_stat)
|
|
{
|
|
if (vdc_stat & _PSB_IRQ_ASLE)
|
|
psb_intel_opregion_asle_intr(dev);
|
|
|
|
if (vdc_stat & _PSB_VSYNC_PIPEA_FLAG)
|
|
gma_pipe_event_handler(dev, 0);
|
|
|
|
if (vdc_stat & _PSB_VSYNC_PIPEB_FLAG)
|
|
gma_pipe_event_handler(dev, 1);
|
|
}
|
|
|
|
/*
|
|
* SGX interrupt handler
|
|
*/
|
|
static void gma_sgx_interrupt(struct drm_device *dev, u32 stat_1, u32 stat_2)
|
|
{
|
|
struct drm_psb_private *dev_priv = to_drm_psb_private(dev);
|
|
u32 val, addr;
|
|
|
|
if (stat_1 & _PSB_CE_TWOD_COMPLETE)
|
|
val = PSB_RSGX32(PSB_CR_2D_BLIT_STATUS);
|
|
|
|
if (stat_2 & _PSB_CE2_BIF_REQUESTER_FAULT) {
|
|
val = PSB_RSGX32(PSB_CR_BIF_INT_STAT);
|
|
addr = PSB_RSGX32(PSB_CR_BIF_FAULT);
|
|
if (val) {
|
|
if (val & _PSB_CBI_STAT_PF_N_RW)
|
|
DRM_ERROR("SGX MMU page fault:");
|
|
else
|
|
DRM_ERROR("SGX MMU read / write protection fault:");
|
|
|
|
if (val & _PSB_CBI_STAT_FAULT_CACHE)
|
|
DRM_ERROR("\tCache requestor");
|
|
if (val & _PSB_CBI_STAT_FAULT_TA)
|
|
DRM_ERROR("\tTA requestor");
|
|
if (val & _PSB_CBI_STAT_FAULT_VDM)
|
|
DRM_ERROR("\tVDM requestor");
|
|
if (val & _PSB_CBI_STAT_FAULT_2D)
|
|
DRM_ERROR("\t2D requestor");
|
|
if (val & _PSB_CBI_STAT_FAULT_PBE)
|
|
DRM_ERROR("\tPBE requestor");
|
|
if (val & _PSB_CBI_STAT_FAULT_TSP)
|
|
DRM_ERROR("\tTSP requestor");
|
|
if (val & _PSB_CBI_STAT_FAULT_ISP)
|
|
DRM_ERROR("\tISP requestor");
|
|
if (val & _PSB_CBI_STAT_FAULT_USSEPDS)
|
|
DRM_ERROR("\tUSSEPDS requestor");
|
|
if (val & _PSB_CBI_STAT_FAULT_HOST)
|
|
DRM_ERROR("\tHost requestor");
|
|
|
|
DRM_ERROR("\tMMU failing address is 0x%08x.\n",
|
|
(unsigned int)addr);
|
|
}
|
|
}
|
|
|
|
/* Clear bits */
|
|
PSB_WSGX32(stat_1, PSB_CR_EVENT_HOST_CLEAR);
|
|
PSB_WSGX32(stat_2, PSB_CR_EVENT_HOST_CLEAR2);
|
|
PSB_RSGX32(PSB_CR_EVENT_HOST_CLEAR2);
|
|
}
|
|
|
|
static irqreturn_t gma_irq_handler(int irq, void *arg)
|
|
{
|
|
struct drm_device *dev = arg;
|
|
struct drm_psb_private *dev_priv = to_drm_psb_private(dev);
|
|
uint32_t vdc_stat, dsp_int = 0, sgx_int = 0, hotplug_int = 0;
|
|
u32 sgx_stat_1, sgx_stat_2;
|
|
int handled = 0;
|
|
|
|
spin_lock(&dev_priv->irqmask_lock);
|
|
|
|
vdc_stat = PSB_RVDC32(PSB_INT_IDENTITY_R);
|
|
|
|
if (vdc_stat & (_PSB_PIPE_EVENT_FLAG|_PSB_IRQ_ASLE))
|
|
dsp_int = 1;
|
|
|
|
if (vdc_stat & _PSB_IRQ_SGX_FLAG)
|
|
sgx_int = 1;
|
|
if (vdc_stat & _PSB_IRQ_DISP_HOTSYNC)
|
|
hotplug_int = 1;
|
|
|
|
vdc_stat &= dev_priv->vdc_irq_mask;
|
|
spin_unlock(&dev_priv->irqmask_lock);
|
|
|
|
if (dsp_int) {
|
|
gma_vdc_interrupt(dev, vdc_stat);
|
|
handled = 1;
|
|
}
|
|
|
|
if (sgx_int) {
|
|
sgx_stat_1 = PSB_RSGX32(PSB_CR_EVENT_STATUS);
|
|
sgx_stat_2 = PSB_RSGX32(PSB_CR_EVENT_STATUS2);
|
|
gma_sgx_interrupt(dev, sgx_stat_1, sgx_stat_2);
|
|
handled = 1;
|
|
}
|
|
|
|
/* Note: this bit has other meanings on some devices, so we will
|
|
need to address that later if it ever matters */
|
|
if (hotplug_int && dev_priv->ops->hotplug) {
|
|
handled = dev_priv->ops->hotplug(dev);
|
|
REG_WRITE(PORT_HOTPLUG_STAT, REG_READ(PORT_HOTPLUG_STAT));
|
|
}
|
|
|
|
PSB_WVDC32(vdc_stat, PSB_INT_IDENTITY_R);
|
|
(void) PSB_RVDC32(PSB_INT_IDENTITY_R);
|
|
rmb();
|
|
|
|
if (!handled)
|
|
return IRQ_NONE;
|
|
|
|
return IRQ_HANDLED;
|
|
}
|
|
|
|
void gma_irq_preinstall(struct drm_device *dev)
|
|
{
|
|
struct drm_psb_private *dev_priv = to_drm_psb_private(dev);
|
|
unsigned long irqflags;
|
|
|
|
spin_lock_irqsave(&dev_priv->irqmask_lock, irqflags);
|
|
|
|
PSB_WVDC32(0xFFFFFFFF, PSB_HWSTAM);
|
|
PSB_WVDC32(0x00000000, PSB_INT_MASK_R);
|
|
PSB_WVDC32(0x00000000, PSB_INT_ENABLE_R);
|
|
PSB_WSGX32(0x00000000, PSB_CR_EVENT_HOST_ENABLE);
|
|
PSB_RSGX32(PSB_CR_EVENT_HOST_ENABLE);
|
|
|
|
if (dev->vblank[0].enabled)
|
|
dev_priv->vdc_irq_mask |= _PSB_VSYNC_PIPEA_FLAG;
|
|
if (dev->vblank[1].enabled)
|
|
dev_priv->vdc_irq_mask |= _PSB_VSYNC_PIPEB_FLAG;
|
|
|
|
/* Revisit this area - want per device masks ? */
|
|
if (dev_priv->ops->hotplug)
|
|
dev_priv->vdc_irq_mask |= _PSB_IRQ_DISP_HOTSYNC;
|
|
dev_priv->vdc_irq_mask |= _PSB_IRQ_ASLE | _PSB_IRQ_SGX_FLAG;
|
|
|
|
/* This register is safe even if display island is off */
|
|
PSB_WVDC32(~dev_priv->vdc_irq_mask, PSB_INT_MASK_R);
|
|
spin_unlock_irqrestore(&dev_priv->irqmask_lock, irqflags);
|
|
}
|
|
|
|
void gma_irq_postinstall(struct drm_device *dev)
|
|
{
|
|
struct drm_psb_private *dev_priv = to_drm_psb_private(dev);
|
|
unsigned long irqflags;
|
|
unsigned int i;
|
|
|
|
spin_lock_irqsave(&dev_priv->irqmask_lock, irqflags);
|
|
|
|
/* Enable 2D and MMU fault interrupts */
|
|
PSB_WSGX32(_PSB_CE2_BIF_REQUESTER_FAULT, PSB_CR_EVENT_HOST_ENABLE2);
|
|
PSB_WSGX32(_PSB_CE_TWOD_COMPLETE, PSB_CR_EVENT_HOST_ENABLE);
|
|
PSB_RSGX32(PSB_CR_EVENT_HOST_ENABLE); /* Post */
|
|
|
|
/* This register is safe even if display island is off */
|
|
PSB_WVDC32(dev_priv->vdc_irq_mask, PSB_INT_ENABLE_R);
|
|
PSB_WVDC32(0xFFFFFFFF, PSB_HWSTAM);
|
|
|
|
for (i = 0; i < dev->num_crtcs; ++i) {
|
|
if (dev->vblank[i].enabled)
|
|
gma_enable_pipestat(dev_priv, i, PIPE_VBLANK_INTERRUPT_ENABLE);
|
|
else
|
|
gma_disable_pipestat(dev_priv, i, PIPE_VBLANK_INTERRUPT_ENABLE);
|
|
}
|
|
|
|
if (dev_priv->ops->hotplug_enable)
|
|
dev_priv->ops->hotplug_enable(dev, true);
|
|
|
|
spin_unlock_irqrestore(&dev_priv->irqmask_lock, irqflags);
|
|
}
|
|
|
|
int gma_irq_install(struct drm_device *dev)
|
|
{
|
|
struct drm_psb_private *dev_priv = to_drm_psb_private(dev);
|
|
struct pci_dev *pdev = to_pci_dev(dev->dev);
|
|
int ret;
|
|
|
|
if (dev_priv->use_msi && pci_enable_msi(pdev)) {
|
|
dev_warn(dev->dev, "Enabling MSI failed!\n");
|
|
dev_priv->use_msi = false;
|
|
}
|
|
|
|
if (pdev->irq == IRQ_NOTCONNECTED)
|
|
return -ENOTCONN;
|
|
|
|
gma_irq_preinstall(dev);
|
|
|
|
/* PCI devices require shared interrupts. */
|
|
ret = request_irq(pdev->irq, gma_irq_handler, IRQF_SHARED, dev->driver->name, dev);
|
|
if (ret)
|
|
return ret;
|
|
|
|
gma_irq_postinstall(dev);
|
|
|
|
dev_priv->irq_enabled = true;
|
|
|
|
return 0;
|
|
}
|
|
|
|
void gma_irq_uninstall(struct drm_device *dev)
|
|
{
|
|
struct drm_psb_private *dev_priv = to_drm_psb_private(dev);
|
|
struct pci_dev *pdev = to_pci_dev(dev->dev);
|
|
unsigned long irqflags;
|
|
unsigned int i;
|
|
|
|
if (!dev_priv->irq_enabled)
|
|
return;
|
|
|
|
spin_lock_irqsave(&dev_priv->irqmask_lock, irqflags);
|
|
|
|
if (dev_priv->ops->hotplug_enable)
|
|
dev_priv->ops->hotplug_enable(dev, false);
|
|
|
|
PSB_WVDC32(0xFFFFFFFF, PSB_HWSTAM);
|
|
|
|
for (i = 0; i < dev->num_crtcs; ++i) {
|
|
if (dev->vblank[i].enabled)
|
|
gma_disable_pipestat(dev_priv, i, PIPE_VBLANK_INTERRUPT_ENABLE);
|
|
}
|
|
|
|
dev_priv->vdc_irq_mask &= _PSB_IRQ_SGX_FLAG |
|
|
_PSB_IRQ_MSVDX_FLAG |
|
|
_LNC_IRQ_TOPAZ_FLAG;
|
|
|
|
/* These two registers are safe even if display island is off */
|
|
PSB_WVDC32(~dev_priv->vdc_irq_mask, PSB_INT_MASK_R);
|
|
PSB_WVDC32(dev_priv->vdc_irq_mask, PSB_INT_ENABLE_R);
|
|
|
|
wmb();
|
|
|
|
/* This register is safe even if display island is off */
|
|
PSB_WVDC32(PSB_RVDC32(PSB_INT_IDENTITY_R), PSB_INT_IDENTITY_R);
|
|
spin_unlock_irqrestore(&dev_priv->irqmask_lock, irqflags);
|
|
|
|
free_irq(pdev->irq, dev);
|
|
if (dev_priv->use_msi)
|
|
pci_disable_msi(pdev);
|
|
}
|
|
|
|
int gma_crtc_enable_vblank(struct drm_crtc *crtc)
|
|
{
|
|
struct drm_device *dev = crtc->dev;
|
|
unsigned int pipe = crtc->index;
|
|
struct drm_psb_private *dev_priv = to_drm_psb_private(dev);
|
|
unsigned long irqflags;
|
|
uint32_t reg_val = 0;
|
|
uint32_t pipeconf_reg = gma_pipeconf(pipe);
|
|
|
|
if (gma_power_begin(dev, false)) {
|
|
reg_val = REG_READ(pipeconf_reg);
|
|
gma_power_end(dev);
|
|
}
|
|
|
|
if (!(reg_val & PIPEACONF_ENABLE))
|
|
return -EINVAL;
|
|
|
|
spin_lock_irqsave(&dev_priv->irqmask_lock, irqflags);
|
|
|
|
if (pipe == 0)
|
|
dev_priv->vdc_irq_mask |= _PSB_VSYNC_PIPEA_FLAG;
|
|
else if (pipe == 1)
|
|
dev_priv->vdc_irq_mask |= _PSB_VSYNC_PIPEB_FLAG;
|
|
|
|
PSB_WVDC32(~dev_priv->vdc_irq_mask, PSB_INT_MASK_R);
|
|
PSB_WVDC32(dev_priv->vdc_irq_mask, PSB_INT_ENABLE_R);
|
|
gma_enable_pipestat(dev_priv, pipe, PIPE_VBLANK_INTERRUPT_ENABLE);
|
|
|
|
spin_unlock_irqrestore(&dev_priv->irqmask_lock, irqflags);
|
|
|
|
return 0;
|
|
}
|
|
|
|
void gma_crtc_disable_vblank(struct drm_crtc *crtc)
|
|
{
|
|
struct drm_device *dev = crtc->dev;
|
|
unsigned int pipe = crtc->index;
|
|
struct drm_psb_private *dev_priv = to_drm_psb_private(dev);
|
|
unsigned long irqflags;
|
|
|
|
spin_lock_irqsave(&dev_priv->irqmask_lock, irqflags);
|
|
|
|
if (pipe == 0)
|
|
dev_priv->vdc_irq_mask &= ~_PSB_VSYNC_PIPEA_FLAG;
|
|
else if (pipe == 1)
|
|
dev_priv->vdc_irq_mask &= ~_PSB_VSYNC_PIPEB_FLAG;
|
|
|
|
PSB_WVDC32(~dev_priv->vdc_irq_mask, PSB_INT_MASK_R);
|
|
PSB_WVDC32(dev_priv->vdc_irq_mask, PSB_INT_ENABLE_R);
|
|
gma_disable_pipestat(dev_priv, pipe, PIPE_VBLANK_INTERRUPT_ENABLE);
|
|
|
|
spin_unlock_irqrestore(&dev_priv->irqmask_lock, irqflags);
|
|
}
|
|
|
|
/* Called from drm generic code, passed a 'crtc', which
|
|
* we use as a pipe index
|
|
*/
|
|
u32 gma_crtc_get_vblank_counter(struct drm_crtc *crtc)
|
|
{
|
|
struct drm_device *dev = crtc->dev;
|
|
unsigned int pipe = crtc->index;
|
|
uint32_t high_frame = PIPEAFRAMEHIGH;
|
|
uint32_t low_frame = PIPEAFRAMEPIXEL;
|
|
uint32_t pipeconf_reg = PIPEACONF;
|
|
uint32_t reg_val = 0;
|
|
uint32_t high1 = 0, high2 = 0, low = 0, count = 0;
|
|
|
|
switch (pipe) {
|
|
case 0:
|
|
break;
|
|
case 1:
|
|
high_frame = PIPEBFRAMEHIGH;
|
|
low_frame = PIPEBFRAMEPIXEL;
|
|
pipeconf_reg = PIPEBCONF;
|
|
break;
|
|
case 2:
|
|
high_frame = PIPECFRAMEHIGH;
|
|
low_frame = PIPECFRAMEPIXEL;
|
|
pipeconf_reg = PIPECCONF;
|
|
break;
|
|
default:
|
|
dev_err(dev->dev, "%s, invalid pipe.\n", __func__);
|
|
return 0;
|
|
}
|
|
|
|
if (!gma_power_begin(dev, false))
|
|
return 0;
|
|
|
|
reg_val = REG_READ(pipeconf_reg);
|
|
|
|
if (!(reg_val & PIPEACONF_ENABLE)) {
|
|
dev_err(dev->dev, "trying to get vblank count for disabled pipe %u\n",
|
|
pipe);
|
|
goto err_gma_power_end;
|
|
}
|
|
|
|
/*
|
|
* High & low register fields aren't synchronized, so make sure
|
|
* we get a low value that's stable across two reads of the high
|
|
* register.
|
|
*/
|
|
do {
|
|
high1 = ((REG_READ(high_frame) & PIPE_FRAME_HIGH_MASK) >>
|
|
PIPE_FRAME_HIGH_SHIFT);
|
|
low = ((REG_READ(low_frame) & PIPE_FRAME_LOW_MASK) >>
|
|
PIPE_FRAME_LOW_SHIFT);
|
|
high2 = ((REG_READ(high_frame) & PIPE_FRAME_HIGH_MASK) >>
|
|
PIPE_FRAME_HIGH_SHIFT);
|
|
} while (high1 != high2);
|
|
|
|
count = (high1 << 8) | low;
|
|
|
|
err_gma_power_end:
|
|
gma_power_end(dev);
|
|
|
|
return count;
|
|
}
|
|
|