/* * Copyright 2006, Haiku, Inc. All Rights Reserved. * Copyright 2002-2004, Thomas Kurschel * * Distributed under the terms of the MIT License. */ #include "mmio.h" #include "radeon_driver.h" #include "rbbm_regs.h" #include /** Disable all interrupts */ static void Radeon_DisableIRQ(device_info *di) { OUTREG(di->regs, RADEON_GEN_INT_CNTL, 0); } /** Interrupt worker routine * handles standard interrupts, i.e. VBI and DMA */ static int32 Radeon_ThreadInterruptWork(vuint8 *regs, device_info *di, uint32 int_status) { shared_info *si = di->si; int32 handled = B_HANDLED_INTERRUPT; if ((int_status & RADEON_CRTC_VBLANK_STAT) != 0 && si->crtc[0].vblank >= 0) { int32 blocked; ++di->vbi_count[0]; if (get_sem_count(si->crtc[0].vblank, &blocked ) == B_OK && blocked < 0) { release_sem_etc(si->crtc[0].vblank, -blocked, B_DO_NOT_RESCHEDULE); handled = B_INVOKE_SCHEDULER; } } if ((int_status & RADEON_CRTC2_VBLANK_STAT) != 0 && si->crtc[1].vblank >= 0) { int32 blocked; ++di->vbi_count[1]; if (get_sem_count(si->crtc[1].vblank, &blocked) == B_OK && blocked < 0) { release_sem_etc(si->crtc[1].vblank, -blocked, B_DO_NOT_RESCHEDULE); handled = B_INVOKE_SCHEDULER; } } if ((int_status & RADEON_VIDDMA_STAT) != 0) { release_sem_etc(di->dma_sem, 1, B_DO_NOT_RESCHEDULE); handled = B_INVOKE_SCHEDULER; } return handled; } /** Capture interrupt handler */ static int32 Radeon_HandleCaptureInterrupt(vuint8 *regs, device_info *di, uint32 cap_status) { int32 blocked; uint32 handled = B_HANDLED_INTERRUPT; cpu_status prev_irq_state = disable_interrupts(); acquire_spinlock(&di->cap_spinlock); ++di->cap_counter; di->cap_int_status = cap_status; di->cap_timestamp = system_time(); release_spinlock(&di->cap_spinlock); restore_interrupts(prev_irq_state); // don't release if semaphore count is positive, i.e. notifications are piling up if (get_sem_count(di->cap_sem, &blocked) == B_OK && blocked <= 0) { release_sem_etc(di->cap_sem, 1, B_DO_NOT_RESCHEDULE); handled = B_INVOKE_SCHEDULER; } // acknowledge IRQ OUTREG(regs, RADEON_CAP_INT_STATUS, cap_status); return handled; } /** Main interrupt handler */ static int32 Radeon_Interrupt(void *data) { int32 handled = B_UNHANDLED_INTERRUPT; device_info *di = (device_info *)data; vuint8 *regs = di->regs; int32 full_int_status, int_status; // read possible IRQ reasons, ignoring any masked IRQs full_int_status = INREG(regs, RADEON_GEN_INT_STATUS); int_status = full_int_status & INREG(regs, RADEON_GEN_INT_CNTL); if (int_status != 0) { ++di->interrupt_count; handled = Radeon_ThreadInterruptWork(regs, di, int_status); // acknowledge IRQ OUTREG(regs, RADEON_GEN_INT_STATUS, int_status); } // capture interrupt have no mask in GEN_INT_CNTL register; // probably, ATI wanted to make capture interrupt control independant of main control if ((full_int_status & RADEON_CAP0_INT_ACTIVE) != 0) { int32 cap_status; // same as before: only regard enabled IRQ reasons cap_status = INREG(regs, RADEON_CAP_INT_STATUS); cap_status &= INREG(regs, RADEON_CAP_INT_CNTL); if (cap_status != 0) { int32 cap_handled; cap_handled = Radeon_HandleCaptureInterrupt(regs, di, cap_status); if (cap_handled == B_INVOKE_SCHEDULER || handled == B_INVOKE_SCHEDULER) handled = B_INVOKE_SCHEDULER; else if (cap_handled == B_HANDLED_INTERRUPT) handled = B_HANDLED_INTERRUPT; } } return handled; } static int32 timer_interrupt_func(timer *te) { bigtime_t now = system_time(); /* get the pointer to the device we're handling this time */ device_info *di = ((timer_info *)te)->di; shared_info *si = di->si; vuint8 *regs = di->regs; uint32 vbl_status = 0 /* read vertical blank status */; int32 result = B_HANDLED_INTERRUPT; /* are we suppoesed to handle interrupts still? */ if (!di->shutdown_virtual_irq) { /* reschedule with same period by default */ bigtime_t when = si->refresh_period; timer *to; /* if interrupts are "enabled", do our thing */ if (si->enable_virtual_irq) { /* insert code to sync to interrupts here */ if (!vbl_status) { when -= si->blank_period - 4; } /* do the things we do when we notice a vertical retrace */ result = Radeon_ThreadInterruptWork(regs, di, RADEON_CRTC_VBLANK_STAT | (di->num_crtc > 1 ? RADEON_CRTC2_VBLANK_STAT : 0)); } /* pick the "other" timer */ to = (timer *)&di->ti_a; if (to == te) to = (timer *)&di->ti_b; /* our guess as to when we should be back */ ((timer_info *)to)->when_target = now + when; /* reschedule the interrupt */ add_timer(to, timer_interrupt_func, ((timer_info *)to)->when_target, B_ONE_SHOT_ABSOLUTE_TIMER); /* remember the currently active timer */ di->current_timer = (timer_info *)to; } return result; } /** Setup IRQ handlers. * Includes an VBI emulator via a timer (according to sample code), * though this makes sense for one CRTC only. */ status_t Radeon_SetupIRQ(device_info *di, char *buffer) { shared_info *si = di->si; status_t result; thread_id thid; thread_info thinfo; sprintf(buffer, "%04X_%04X_%02X%02X%02X VBI 1", di->pcii.vendor_id, di->pcii.device_id, di->pcii.bus, di->pcii.device, di->pcii.function); si->crtc[0].vblank = create_sem(0, buffer); if (si->crtc[0].vblank < 0) { result = si->crtc[0].vblank; goto err1; } si->crtc[1].vblank = 0; if (di->num_crtc > 1) { sprintf(buffer, "%04X_%04X_%02X%02X%02X VBI 2", di->pcii.vendor_id, di->pcii.device_id, di->pcii.bus, di->pcii.device, di->pcii.function); si->crtc[1].vblank = create_sem(0, buffer); if (si->crtc[1].vblank < 0) { result = si->crtc[1].vblank; goto err2; } } sprintf(buffer, "%04X_%04X_%02X%02X%02X Cap I", di->pcii.vendor_id, di->pcii.device_id, di->pcii.bus, di->pcii.device, di->pcii.function); di->cap_sem = create_sem(0, buffer); if (di->cap_sem < 0) { result = di->cap_sem; goto err3; } B_INITIALIZE_SPINLOCK(&di->cap_spinlock); sprintf(buffer, "%04X_%04X_%02X%02X%02X DMA I", di->pcii.vendor_id, di->pcii.device_id, di->pcii.bus, di->pcii.device, di->pcii.function); di->dma_sem = create_sem(0, buffer); if (di->dma_sem < 0) { result = di->dma_sem; goto err4; } /* change the owner of the semaphores to the opener's team */ /* this is required because apps can't aquire kernel semaphores */ thid = find_thread(NULL); get_thread_info(thid, &thinfo); set_sem_owner(si->crtc[0].vblank, thinfo.team); if (di->num_crtc > 1) set_sem_owner(si->crtc[1].vblank, thinfo.team); //set_sem_owner(di->cap_sem, thinfo.team); /* disable all interrupts */ Radeon_DisableIRQ(di); /* if we're faking interrupts */ if (di->pcii.u.h0.interrupt_pin == 0x00 || di->pcii.u.h0.interrupt_line == 0xff) { SHOW_INFO0( 3, "We like to fake IRQ" ); /* fake some kind of interrupt with a timer */ di->shutdown_virtual_irq = false; si->refresh_period = 16666; /* fake 60Hz to start */ si->blank_period = si->refresh_period / 20; di->ti_a.di = di; /* refer to ourself */ di->ti_b.di = di; di->current_timer = &di->ti_a; /* program the first timer interrupt, and it will handle the rest */ result = add_timer((timer *)(di->current_timer), timer_interrupt_func, si->refresh_period, B_ONE_SHOT_RELATIVE_TIMER); if (result != B_OK) goto err5; } else { /* otherwise install our interrupt handler */ result = install_io_interrupt_handler(di->pcii.u.h0.interrupt_line, Radeon_Interrupt, (void *)di, 0); if (result != B_OK) goto err5; SHOW_INFO(3, "installed IRQ @ %d", di->pcii.u.h0.interrupt_line); } return B_OK; err5: delete_sem(di->dma_sem); err4: delete_sem(di->cap_sem); err3: if (di->num_crtc > 1) delete_sem(si->crtc[1].vblank); err2: delete_sem(si->crtc[0].vblank); err1: return result; } /** Clean-up interrupt handling */ void Radeon_CleanupIRQ(device_info *di) { shared_info *si = di->si; Radeon_DisableIRQ(di); /* if we were faking the interrupts */ if (di->pcii.u.h0.interrupt_pin == 0x00 || di->pcii.u.h0.interrupt_line == 0xff) { /* stop our interrupt faking thread */ di->shutdown_virtual_irq = true; /* cancel the timer */ /* we don't know which one is current, so cancel them both and ignore any error */ cancel_timer((timer *)&di->ti_a); cancel_timer((timer *)&di->ti_b); } else { /* remove interrupt handler */ remove_io_interrupt_handler(di->pcii.u.h0.interrupt_line, Radeon_Interrupt, di); } delete_sem(si->crtc[0].vblank); if (di->num_crtc > 1) delete_sem(si->crtc[1].vblank); delete_sem(di->cap_sem); delete_sem(di->dma_sem); di->cap_sem = si->crtc[1].vblank = si->crtc[0].vblank = 0; }