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
Radeon_DisableIRQ(device_info * di)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
Radeon_ThreadInterruptWork(vuint8 * regs,device_info * di,uint32 int_status)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
Radeon_HandleCaptureInterrupt(vuint8 * regs,device_info * di,uint32 cap_status)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
Radeon_Interrupt(void * data)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
timer_interrupt_func(timer * te)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
Radeon_SetupIRQ(device_info * di,char * buffer)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
Radeon_CleanupIRQ(device_info * di)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