linux/drivers/gpu/drm/gma500/psb_irq.c
Sui Jingfeng da596080b2 drm/gma500: Fix call trace when psb_gem_mm_init() fails
Because the gma_irq_install() is call after psb_gem_mm_init() function,
when psb_gem_mm_init() fails, the interrupt line haven't been allocated.
Yet the gma_irq_uninstall() is called in the psb_driver_unload() function
without checking if checking the irq is registered or not.

The calltrace is appended as following:

[   20.539253] ioremap memtype_reserve failed -16
[   20.543895] gma500 0000:00:02.0: Failure to map stolen base.
[   20.565049] ------------[ cut here ]------------
[   20.565066] Trying to free already-free IRQ 16
[   20.565087] WARNING: CPU: 1 PID: 381 at kernel/irq/manage.c:1893 free_irq+0x209/0x370
[   20.565316] CPU: 1 PID: 381 Comm: systemd-udevd Tainted: G         C         6.5.0-rc1+ #368
[   20.565329] Hardware name: To Be Filled By O.E.M. To Be Filled By O.E.M./IMB-140D Plus, BIOS P1.10 11/18/2013
[   20.565338] RIP: 0010:free_irq+0x209/0x370
[   20.565357] Code: 41 5d 41 5e 41 5f 5d 31 d2 89 d1 89 d6 89 d7 41 89 d1 c3 cc cc cc cc 8b 75 d0 48 c7 c7 e0 77 12 9f 4c 89 4d c8 e8 57 fe f4 ff <0f> 0b 48 8b 75 c8 4c 89 f7 e8 29 f3 f1 00 49 8b 47 40 48 8b 40 78
[   20.565369] RSP: 0018:ffffae3b40733808 EFLAGS: 00010046
[   20.565382] RAX: 0000000000000000 RBX: ffff9f8082bfe000 RCX: 0000000000000000
[   20.565390] RDX: 0000000000000000 RSI: 0000000000000000 RDI: 0000000000000000
[   20.565397] RBP: ffffae3b40733840 R08: 0000000000000000 R09: 0000000000000000
[   20.565405] R10: 0000000000000000 R11: 0000000000000000 R12: ffff9f80871c3100
[   20.565413] R13: ffff9f80835d3360 R14: ffff9f80835d32a4 R15: ffff9f80835d3200
[   20.565424] FS:  00007f13d36458c0(0000) GS:ffff9f8138880000(0000) knlGS:0000000000000000
[   20.565434] CS:  0010 DS: 0000 ES: 0000 CR0: 0000000080050033
[   20.565441] CR2: 00007f0d046f3f20 CR3: 0000000006c8c000 CR4: 00000000000006e0
[   20.565450] Call Trace:
[   20.565458]  <TASK>
[   20.565470]  ? show_regs+0x72/0x90
[   20.565488]  ? free_irq+0x209/0x370
[   20.565504]  ? __warn+0x8d/0x160
[   20.565520]  ? free_irq+0x209/0x370
[   20.565536]  ? report_bug+0x1bb/0x1d0
[   20.565555]  ? handle_bug+0x46/0x90
[   20.565572]  ? exc_invalid_op+0x19/0x80
[   20.565587]  ? asm_exc_invalid_op+0x1b/0x20
[   20.565607]  ? free_irq+0x209/0x370
[   20.565625]  ? free_irq+0x209/0x370
[   20.565644]  gma_irq_uninstall+0x15b/0x1e0 [gma500_gfx]
[   20.565728]  psb_driver_unload+0x27/0x190 [gma500_gfx]
[   20.565800]  psb_pci_probe+0x5d2/0x790 [gma500_gfx]
[   20.565873]  local_pci_probe+0x48/0xb0
[   20.565892]  pci_device_probe+0xc8/0x280
[   20.565912]  really_probe+0x1d2/0x440
[   20.565929]  __driver_probe_device+0x8a/0x190
[   20.565944]  driver_probe_device+0x23/0xd0
[   20.565957]  __driver_attach+0x10f/0x220
[   20.565971]  ? __pfx___driver_attach+0x10/0x10
[   20.565984]  bus_for_each_dev+0x7a/0xe0
[   20.566002]  driver_attach+0x1e/0x30
[   20.566014]  bus_add_driver+0x127/0x240
[   20.566029]  driver_register+0x64/0x140
[   20.566043]  ? __pfx_psb_init+0x10/0x10 [gma500_gfx]
[   20.566111]  __pci_register_driver+0x68/0x80
[   20.566128]  psb_init+0x2c/0xff0 [gma500_gfx]
[   20.566194]  do_one_initcall+0x46/0x330
[   20.566214]  ? kmalloc_trace+0x2a/0xb0
[   20.566233]  do_init_module+0x6a/0x270
[   20.566250]  load_module+0x207f/0x23a0
[   20.566278]  init_module_from_file+0x9c/0xf0
[   20.566293]  ? init_module_from_file+0x9c/0xf0
[   20.566315]  idempotent_init_module+0x184/0x240
[   20.566335]  __x64_sys_finit_module+0x64/0xd0
[   20.566352]  do_syscall_64+0x59/0x90
[   20.566366]  ? ksys_mmap_pgoff+0x123/0x270
[   20.566378]  ? __secure_computing+0x9b/0x110
[   20.566392]  ? exit_to_user_mode_prepare+0x39/0x190
[   20.566406]  ? syscall_exit_to_user_mode+0x2a/0x50
[   20.566420]  ? do_syscall_64+0x69/0x90
[   20.566433]  ? do_syscall_64+0x69/0x90
[   20.566445]  ? do_syscall_64+0x69/0x90
[   20.566458]  entry_SYSCALL_64_after_hwframe+0x6e/0xd8
[   20.566472] RIP: 0033:0x7f13d351ea3d
[   20.566485] Code: 5b 41 5c c3 66 0f 1f 84 00 00 00 00 00 f3 0f 1e fa 48 89 f8 48 89 f7 48 89 d6 48 89 ca 4d 89 c2 4d 89 c8 4c 8b 4c 24 08 0f 05 <48> 3d 01 f0 ff ff 73 01 c3 48 8b 0d c3 a3 0f 00 f7 d8 64 89 01 48
[   20.566496] RSP: 002b:00007ffe566c1fd8 EFLAGS: 00000246 ORIG_RAX: 0000000000000139
[   20.566510] RAX: ffffffffffffffda RBX: 000055e66806eec0 RCX: 00007f13d351ea3d
[   20.566519] RDX: 0000000000000000 RSI: 00007f13d36d9441 RDI: 0000000000000010
[   20.566527] RBP: 0000000000020000 R08: 0000000000000000 R09: 0000000000000002
[   20.566535] R10: 0000000000000010 R11: 0000000000000246 R12: 00007f13d36d9441
[   20.566543] R13: 000055e6681108c0 R14: 000055e66805ba70 R15: 000055e66819a9c0
[   20.566559]  </TASK>
[   20.566566] ---[ end trace 0000000000000000 ]---

Signed-off-by: Sui Jingfeng <suijingfeng@loongson.cn>
Signed-off-by: Patrik Jakobsson <patrik.r.jakobsson@gmail.com>
Link: https://patchwork.freedesktop.org/patch/msgid/20230727185855.713318-1-suijingfeng@loongson.cn
2023-08-25 08:48:18 +02:00

494 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_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;
}