1 /* 2 * Copyright 2006, Haiku, Inc. All Rights Reserved. 3 * Copyright 2002-2004, Thomas Kurschel 4 * 5 * Distributed under the terms of the MIT License. 6 */ 7 8 9 #include "mmio.h" 10 #include "radeon_driver.h" 11 #include "rbbm_regs.h" 12 13 #include <stdio.h> 14 15 16 /** Disable all interrupts */ 17 18 static void 19 Radeon_DisableIRQ(device_info *di) 20 { 21 OUTREG(di->regs, RADEON_GEN_INT_CNTL, 0); 22 } 23 24 25 /** Interrupt worker routine 26 * handles standard interrupts, i.e. VBI and DMA 27 */ 28 29 static int32 30 Radeon_ThreadInterruptWork(vuint8 *regs, device_info *di, uint32 int_status) 31 { 32 shared_info *si = di->si; 33 int32 handled = B_HANDLED_INTERRUPT; 34 35 if ((int_status & RADEON_CRTC_VBLANK_STAT) != 0 36 && si->crtc[0].vblank >= 0) { 37 int32 blocked; 38 39 ++di->vbi_count[0]; 40 41 if (get_sem_count(si->crtc[0].vblank, &blocked ) == B_OK && blocked < 0) { 42 release_sem_etc(si->crtc[0].vblank, -blocked, B_DO_NOT_RESCHEDULE); 43 handled = B_INVOKE_SCHEDULER; 44 } 45 } 46 47 if ((int_status & RADEON_CRTC2_VBLANK_STAT) != 0 48 && si->crtc[1].vblank >= 0) { 49 int32 blocked; 50 51 ++di->vbi_count[1]; 52 53 if (get_sem_count(si->crtc[1].vblank, &blocked) == B_OK && blocked < 0) { 54 release_sem_etc(si->crtc[1].vblank, -blocked, B_DO_NOT_RESCHEDULE); 55 handled = B_INVOKE_SCHEDULER; 56 } 57 } 58 59 if ((int_status & RADEON_VIDDMA_STAT) != 0) { 60 release_sem_etc(di->dma_sem, 1, B_DO_NOT_RESCHEDULE); 61 handled = B_INVOKE_SCHEDULER; 62 } 63 64 return handled; 65 } 66 67 68 /** Capture interrupt handler */ 69 70 static int32 71 Radeon_HandleCaptureInterrupt(vuint8 *regs, device_info *di, uint32 cap_status) 72 { 73 int32 blocked; 74 uint32 handled = B_HANDLED_INTERRUPT; 75 76 cpu_status prev_irq_state = disable_interrupts(); 77 acquire_spinlock(&di->cap_spinlock); 78 79 ++di->cap_counter; 80 di->cap_int_status = cap_status; 81 di->cap_timestamp = system_time(); 82 83 release_spinlock(&di->cap_spinlock); 84 restore_interrupts(prev_irq_state); 85 86 // don't release if semaphore count is positive, i.e. notifications are piling up 87 if (get_sem_count(di->cap_sem, &blocked) == B_OK && blocked <= 0) { 88 release_sem_etc(di->cap_sem, 1, B_DO_NOT_RESCHEDULE); 89 handled = B_INVOKE_SCHEDULER; 90 } 91 92 // acknowledge IRQ 93 OUTREG(regs, RADEON_CAP_INT_STATUS, cap_status); 94 95 return handled; 96 } 97 98 99 /** Main interrupt handler */ 100 101 static int32 102 Radeon_Interrupt(void *data) 103 { 104 int32 handled = B_UNHANDLED_INTERRUPT; 105 device_info *di = (device_info *)data; 106 vuint8 *regs = di->regs; 107 int32 full_int_status, int_status; 108 109 // read possible IRQ reasons, ignoring any masked IRQs 110 full_int_status = INREG(regs, RADEON_GEN_INT_STATUS); 111 int_status = full_int_status & INREG(regs, RADEON_GEN_INT_CNTL); 112 113 if (int_status != 0) { 114 ++di->interrupt_count; 115 116 handled = Radeon_ThreadInterruptWork(regs, di, int_status); 117 118 // acknowledge IRQ 119 OUTREG(regs, RADEON_GEN_INT_STATUS, int_status); 120 } 121 122 // capture interrupt have no mask in GEN_INT_CNTL register; 123 // probably, ATI wanted to make capture interrupt control independant of main control 124 if ((full_int_status & RADEON_CAP0_INT_ACTIVE) != 0) { 125 int32 cap_status; 126 127 // same as before: only regard enabled IRQ reasons 128 cap_status = INREG(regs, RADEON_CAP_INT_STATUS); 129 cap_status &= INREG(regs, RADEON_CAP_INT_CNTL); 130 131 if (cap_status != 0) { 132 int32 cap_handled; 133 134 cap_handled = Radeon_HandleCaptureInterrupt(regs, di, cap_status); 135 136 if (cap_handled == B_INVOKE_SCHEDULER || handled == B_INVOKE_SCHEDULER) 137 handled = B_INVOKE_SCHEDULER; 138 else if (cap_handled == B_HANDLED_INTERRUPT) 139 handled = B_HANDLED_INTERRUPT; 140 } 141 } 142 143 return handled; 144 } 145 146 147 static int32 148 timer_interrupt_func(timer *te) 149 { 150 bigtime_t now = system_time(); 151 /* get the pointer to the device we're handling this time */ 152 device_info *di = ((timer_info *)te)->di; 153 shared_info *si = di->si; 154 vuint8 *regs = di->regs; 155 uint32 vbl_status = 0 /* read vertical blank status */; 156 int32 result = B_HANDLED_INTERRUPT; 157 158 /* are we suppoesed to handle interrupts still? */ 159 if (!di->shutdown_virtual_irq) { 160 /* reschedule with same period by default */ 161 bigtime_t when = si->refresh_period; 162 timer *to; 163 164 /* if interrupts are "enabled", do our thing */ 165 if (si->enable_virtual_irq) { 166 /* insert code to sync to interrupts here */ 167 if (!vbl_status) { 168 when -= si->blank_period - 4; 169 } 170 /* do the things we do when we notice a vertical retrace */ 171 result = Radeon_ThreadInterruptWork(regs, di, 172 RADEON_CRTC_VBLANK_STAT 173 | (di->num_crtc > 1 ? RADEON_CRTC2_VBLANK_STAT : 0)); 174 } 175 176 /* pick the "other" timer */ 177 to = (timer *)&di->ti_a; 178 if (to == te) 179 to = (timer *)&di->ti_b; 180 181 /* our guess as to when we should be back */ 182 ((timer_info *)to)->when_target = now + when; 183 184 /* reschedule the interrupt */ 185 add_timer(to, timer_interrupt_func, ((timer_info *)to)->when_target, 186 B_ONE_SHOT_ABSOLUTE_TIMER); 187 188 /* remember the currently active timer */ 189 di->current_timer = (timer_info *)to; 190 } 191 192 return result; 193 } 194 195 196 /** Setup IRQ handlers. 197 * Includes an VBI emulator via a timer (according to sample code), 198 * though this makes sense for one CRTC only. 199 */ 200 201 status_t 202 Radeon_SetupIRQ(device_info *di, char *buffer) 203 { 204 shared_info *si = di->si; 205 status_t result; 206 thread_id thid; 207 thread_info thinfo; 208 209 sprintf(buffer, "%04X_%04X_%02X%02X%02X VBI 1", 210 di->pcii.vendor_id, di->pcii.device_id, 211 di->pcii.bus, di->pcii.device, di->pcii.function); 212 si->crtc[0].vblank = create_sem(0, buffer); 213 if (si->crtc[0].vblank < 0) { 214 result = si->crtc[0].vblank; 215 goto err1; 216 } 217 218 si->crtc[1].vblank = 0; 219 220 if (di->num_crtc > 1) { 221 sprintf(buffer, "%04X_%04X_%02X%02X%02X VBI 2", 222 di->pcii.vendor_id, di->pcii.device_id, 223 di->pcii.bus, di->pcii.device, di->pcii.function); 224 si->crtc[1].vblank = create_sem(0, buffer); 225 if (si->crtc[1].vblank < 0) { 226 result = si->crtc[1].vblank; 227 goto err2; 228 } 229 } 230 231 sprintf(buffer, "%04X_%04X_%02X%02X%02X Cap I", 232 di->pcii.vendor_id, di->pcii.device_id, 233 di->pcii.bus, di->pcii.device, di->pcii.function); 234 di->cap_sem = create_sem(0, buffer); 235 if (di->cap_sem < 0) { 236 result = di->cap_sem; 237 goto err3; 238 } 239 240 B_INITIALIZE_SPINLOCK(&di->cap_spinlock); 241 242 sprintf(buffer, "%04X_%04X_%02X%02X%02X DMA I", 243 di->pcii.vendor_id, di->pcii.device_id, 244 di->pcii.bus, di->pcii.device, di->pcii.function); 245 di->dma_sem = create_sem(0, buffer); 246 if (di->dma_sem < 0) { 247 result = di->dma_sem; 248 goto err4; 249 } 250 251 /* change the owner of the semaphores to the opener's team */ 252 /* this is required because apps can't aquire kernel semaphores */ 253 thid = find_thread(NULL); 254 get_thread_info(thid, &thinfo); 255 set_sem_owner(si->crtc[0].vblank, thinfo.team); 256 if (di->num_crtc > 1) 257 set_sem_owner(si->crtc[1].vblank, thinfo.team); 258 //set_sem_owner(di->cap_sem, thinfo.team); 259 260 /* disable all interrupts */ 261 Radeon_DisableIRQ(di); 262 263 /* if we're faking interrupts */ 264 if (di->pcii.u.h0.interrupt_pin == 0x00 || di->pcii.u.h0.interrupt_line == 0xff) { 265 SHOW_INFO0( 3, "We like to fake IRQ" ); 266 /* fake some kind of interrupt with a timer */ 267 di->shutdown_virtual_irq = false; 268 si->refresh_period = 16666; /* fake 60Hz to start */ 269 si->blank_period = si->refresh_period / 20; 270 271 di->ti_a.di = di; /* refer to ourself */ 272 di->ti_b.di = di; 273 di->current_timer = &di->ti_a; 274 275 /* program the first timer interrupt, and it will handle the rest */ 276 result = add_timer((timer *)(di->current_timer), timer_interrupt_func, 277 si->refresh_period, B_ONE_SHOT_RELATIVE_TIMER); 278 if (result != B_OK) 279 goto err5; 280 } else { 281 /* otherwise install our interrupt handler */ 282 result = install_io_interrupt_handler(di->pcii.u.h0.interrupt_line, 283 Radeon_Interrupt, (void *)di, 0); 284 if (result != B_OK) 285 goto err5; 286 287 SHOW_INFO(3, "installed IRQ @ %d", di->pcii.u.h0.interrupt_line); 288 } 289 290 return B_OK; 291 292 err5: 293 delete_sem(di->dma_sem); 294 err4: 295 delete_sem(di->cap_sem); 296 err3: 297 if (di->num_crtc > 1) 298 delete_sem(si->crtc[1].vblank); 299 err2: 300 delete_sem(si->crtc[0].vblank); 301 err1: 302 return result; 303 } 304 305 306 /** Clean-up interrupt handling */ 307 308 void 309 Radeon_CleanupIRQ(device_info *di) 310 { 311 shared_info *si = di->si; 312 313 Radeon_DisableIRQ(di); 314 315 /* if we were faking the interrupts */ 316 if (di->pcii.u.h0.interrupt_pin == 0x00 || di->pcii.u.h0.interrupt_line == 0xff) { 317 /* stop our interrupt faking thread */ 318 di->shutdown_virtual_irq = true; 319 /* cancel the timer */ 320 /* we don't know which one is current, so cancel them both and ignore any error */ 321 cancel_timer((timer *)&di->ti_a); 322 cancel_timer((timer *)&di->ti_b); 323 } else { 324 /* remove interrupt handler */ 325 remove_io_interrupt_handler(di->pcii.u.h0.interrupt_line, Radeon_Interrupt, di); 326 } 327 328 delete_sem(si->crtc[0].vblank); 329 330 if (di->num_crtc > 1) 331 delete_sem(si->crtc[1].vblank); 332 333 delete_sem(di->cap_sem); 334 delete_sem(di->dma_sem); 335 336 di->cap_sem = si->crtc[1].vblank = si->crtc[0].vblank = 0; 337 } 338