xref: /haiku/src/add-ons/kernel/drivers/graphics/radeon/irq.c (revision 1deede7388b04dbeec5af85cae7164735ea9e70d)
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