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
18static void
19Radeon_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
29static int32
30Radeon_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
70static int32
71Radeon_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
101static int32
102Radeon_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
147static int32
148timer_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
201status_t
202Radeon_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
292err5:
293	delete_sem(di->dma_sem);
294err4:
295	delete_sem(di->cap_sem);
296err3:
297	if (di->num_crtc > 1)
298		delete_sem(si->crtc[1].vblank);
299err2:
300	delete_sem(si->crtc[0].vblank);
301err1:
302	return result;
303}
304
305
306/**	Clean-up interrupt handling */
307
308void
309Radeon_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