aboutsummaryrefslogtreecommitdiffstats
path: root/sound/pci/ctxfi/cttimer.c
diff options
context:
space:
mode:
authorTakashi Iwai <tiwai@suse.de>2009-06-05 10:11:07 -0400
committerTakashi Iwai <tiwai@suse.de>2009-06-05 10:44:13 -0400
commitb7bbf876087e0e2c0ba723a8398083c9a9ac1dfd (patch)
tree69a3e70658fc751ffc99eef5a6f047b19f61a4a2 /sound/pci/ctxfi/cttimer.c
parent6bc5874a1ddf98ac0fe6c4eab7d286c11cb1c748 (diff)
ALSA: ctxfi - Use native timer interrupt on emu20k1
emu20k1 has a native timer interrupt based on the audio clock, which is more accurate than the system timer (from the synchronization POV). This patch adds the code to handle this with multiple streams. The system timer is still used on emu20k2, and can be used also for emu20k1 easily by changing USE_SYSTEM_TIMER to 1 in cttimer.c. Signed-off-by: Takashi Iwai <tiwai@suse.de>
Diffstat (limited to 'sound/pci/ctxfi/cttimer.c')
-rw-r--r--sound/pci/ctxfi/cttimer.c417
1 files changed, 417 insertions, 0 deletions
diff --git a/sound/pci/ctxfi/cttimer.c b/sound/pci/ctxfi/cttimer.c
new file mode 100644
index 000000000000..3acb26d0c4cc
--- /dev/null
+++ b/sound/pci/ctxfi/cttimer.c
@@ -0,0 +1,417 @@
1/*
2 * PCM timer handling on ctxfi
3 *
4 * This source file is released under GPL v2 license (no other versions).
5 * See the COPYING file included in the main directory of this source
6 * distribution for the license terms and conditions.
7 */
8
9#include <linux/slab.h>
10#include <sound/core.h>
11#include <sound/pcm.h>
12#include "ctatc.h"
13#include "cthardware.h"
14#include "cttimer.h"
15
16struct ct_timer_ops {
17 void (*init)(struct ct_timer_instance *);
18 void (*prepare)(struct ct_timer_instance *);
19 void (*start)(struct ct_timer_instance *);
20 void (*stop)(struct ct_timer_instance *);
21 void (*free_instance)(struct ct_timer_instance *);
22 void (*interrupt)(struct ct_timer *);
23 void (*free_global)(struct ct_timer *);
24};
25
26/* timer instance -- assigned to each PCM stream */
27struct ct_timer_instance {
28 spinlock_t lock;
29 struct ct_timer *timer_base;
30 struct ct_atc_pcm *apcm;
31 struct snd_pcm_substream *substream;
32 struct timer_list timer;
33 struct list_head instance_list;
34 struct list_head running_list;
35 unsigned int position;
36 unsigned int frag_count;
37 unsigned int running:1;
38 unsigned int need_update:1;
39};
40
41/* timer instance manager */
42struct ct_timer {
43 spinlock_t lock; /* global timer lock (for xfitimer) */
44 spinlock_t list_lock; /* lock for instance list */
45 struct ct_atc *atc;
46 struct ct_timer_ops *ops;
47 struct list_head instance_head;
48 struct list_head running_head;
49 unsigned int irq_handling:1; /* in IRQ handling */
50 unsigned int reprogram:1; /* need to reprogram the internval */
51 unsigned int running:1; /* global timer running */
52};
53
54
55/*
56 * system-timer-based updates
57 */
58
59static void ct_systimer_callback(unsigned long data)
60{
61 struct ct_timer_instance *ti = (struct ct_timer_instance *)data;
62 struct snd_pcm_substream *substream = ti->substream;
63 struct snd_pcm_runtime *runtime = substream->runtime;
64 struct ct_atc_pcm *apcm = ti->apcm;
65 unsigned int period_size = runtime->period_size;
66 unsigned int buffer_size = runtime->buffer_size;
67 unsigned long flags;
68 unsigned int position, dist, interval;
69
70 position = substream->ops->pointer(substream);
71 dist = (position + buffer_size - ti->position) % buffer_size;
72 if (dist >= period_size ||
73 position / period_size != ti->position / period_size) {
74 apcm->interrupt(apcm);
75 ti->position = position;
76 }
77 /* Add extra HZ*5/1000 to avoid overrun issue when recording
78 * at 8kHz in 8-bit format or at 88kHz in 24-bit format. */
79 interval = ((period_size - (position % period_size))
80 * HZ + (runtime->rate - 1)) / runtime->rate + HZ * 5 / 1000;
81 spin_lock_irqsave(&ti->lock, flags);
82 if (ti->running)
83 mod_timer(&ti->timer, jiffies + interval);
84 spin_unlock_irqrestore(&ti->lock, flags);
85}
86
87static void ct_systimer_init(struct ct_timer_instance *ti)
88{
89 setup_timer(&ti->timer, ct_systimer_callback,
90 (unsigned long)ti);
91}
92
93static void ct_systimer_start(struct ct_timer_instance *ti)
94{
95 struct snd_pcm_runtime *runtime = ti->substream->runtime;
96 unsigned long flags;
97
98 spin_lock_irqsave(&ti->lock, flags);
99 ti->running = 1;
100 mod_timer(&ti->timer,
101 jiffies + (runtime->period_size * HZ +
102 (runtime->rate - 1)) / runtime->rate);
103 spin_unlock_irqrestore(&ti->lock, flags);
104}
105
106static void ct_systimer_stop(struct ct_timer_instance *ti)
107{
108 unsigned long flags;
109
110 spin_lock_irqsave(&ti->lock, flags);
111 ti->running = 0;
112 del_timer(&ti->timer);
113 spin_unlock_irqrestore(&ti->lock, flags);
114}
115
116static void ct_systimer_prepare(struct ct_timer_instance *ti)
117{
118 ct_systimer_stop(ti);
119 try_to_del_timer_sync(&ti->timer);
120}
121
122#define ct_systimer_free ct_systimer_prepare
123
124static struct ct_timer_ops ct_systimer_ops = {
125 .init = ct_systimer_init,
126 .free_instance = ct_systimer_free,
127 .prepare = ct_systimer_prepare,
128 .start = ct_systimer_start,
129 .stop = ct_systimer_stop,
130};
131
132
133/*
134 * Handling multiple streams using a global emu20k1 timer irq
135 */
136
137#define CT_TIMER_FREQ 48000
138#define MAX_TICKS ((1 << 13) - 1)
139
140static void ct_xfitimer_irq_rearm(struct ct_timer *atimer, int ticks)
141{
142 struct hw *hw = atimer->atc->hw;
143 if (ticks > MAX_TICKS)
144 ticks = MAX_TICKS;
145 hw->set_timer_tick(hw, ticks);
146 if (!atimer->running)
147 hw->set_timer_irq(hw, 1);
148 atimer->running = 1;
149}
150
151static void ct_xfitimer_irq_stop(struct ct_timer *atimer)
152{
153 if (atimer->running) {
154 struct hw *hw = atimer->atc->hw;
155 hw->set_timer_irq(hw, 0);
156 hw->set_timer_tick(hw, 0);
157 atimer->running = 0;
158 }
159}
160
161/*
162 * reprogram the timer interval;
163 * checks the running instance list and determines the next timer interval.
164 * also updates the each stream position, returns the number of streams
165 * to call snd_pcm_period_elapsed() appropriately
166 *
167 * call this inside the lock and irq disabled
168 */
169static int ct_xfitimer_reprogram(struct ct_timer *atimer)
170{
171 struct ct_timer_instance *ti;
172 int min_intr = -1;
173 int updates = 0;
174
175 list_for_each_entry(ti, &atimer->running_head, running_list) {
176 struct snd_pcm_runtime *runtime;
177 unsigned int pos, diff;
178 int intr;
179 runtime = ti->substream->runtime;
180 pos = ti->substream->ops->pointer(ti->substream);
181 if (pos < ti->position)
182 diff = runtime->buffer_size - ti->position + pos;
183 else
184 diff = pos - ti->position;
185 ti->position = pos;
186 while (diff >= ti->frag_count) {
187 ti->frag_count += runtime->period_size;
188 ti->need_update = 1;
189 updates++;
190 }
191 ti->frag_count -= diff;
192 intr = div_u64((u64)ti->frag_count * CT_TIMER_FREQ,
193 runtime->rate);
194 if (min_intr < 0 || intr < min_intr)
195 min_intr = intr;
196 }
197
198 if (min_intr > 0)
199 ct_xfitimer_irq_rearm(atimer, min_intr);
200 else
201 ct_xfitimer_irq_stop(atimer);
202
203 atimer->reprogram = 0; /* clear flag */
204 return updates;
205}
206
207/* look through the instance list and call period_elapsed if needed */
208static void ct_xfitimer_check_period(struct ct_timer *atimer)
209{
210 struct ct_timer_instance *ti;
211 unsigned long flags;
212
213 spin_lock_irqsave(&atimer->list_lock, flags);
214 list_for_each_entry(ti, &atimer->instance_head, instance_list) {
215 if (ti->need_update) {
216 ti->need_update = 0;
217 ti->apcm->interrupt(ti->apcm);
218 }
219 }
220 spin_unlock_irqrestore(&atimer->list_lock, flags);
221}
222
223/* Handle timer-interrupt */
224static void ct_xfitimer_callback(struct ct_timer *atimer)
225{
226 int update;
227 unsigned long flags;
228
229 spin_lock_irqsave(&atimer->lock, flags);
230 atimer->irq_handling = 1;
231 do {
232 update = ct_xfitimer_reprogram(atimer);
233 spin_unlock(&atimer->lock);
234 if (update)
235 ct_xfitimer_check_period(atimer);
236 spin_lock(&atimer->lock);
237 } while (atimer->reprogram);
238 atimer->irq_handling = 0;
239 spin_unlock_irqrestore(&atimer->lock, flags);
240}
241
242static void ct_xfitimer_prepare(struct ct_timer_instance *ti)
243{
244 ti->frag_count = ti->substream->runtime->period_size;
245 ti->need_update = 0;
246}
247
248
249/* start/stop the timer */
250static void ct_xfitimer_update(struct ct_timer *atimer)
251{
252 unsigned long flags;
253 int update;
254
255 if (atimer->irq_handling) {
256 /* reached from IRQ handler; let it handle later */
257 atimer->reprogram = 1;
258 return;
259 }
260
261 spin_lock_irqsave(&atimer->lock, flags);
262 ct_xfitimer_irq_stop(atimer);
263 update = ct_xfitimer_reprogram(atimer);
264 spin_unlock_irqrestore(&atimer->lock, flags);
265 if (update)
266 ct_xfitimer_check_period(atimer);
267}
268
269static void ct_xfitimer_start(struct ct_timer_instance *ti)
270{
271 struct ct_timer *atimer = ti->timer_base;
272 unsigned long flags;
273
274 spin_lock_irqsave(&atimer->lock, flags);
275 list_add(&ti->running_list, &atimer->running_head);
276 spin_unlock_irqrestore(&atimer->lock, flags);
277 ct_xfitimer_update(atimer);
278}
279
280static void ct_xfitimer_stop(struct ct_timer_instance *ti)
281{
282 struct ct_timer *atimer = ti->timer_base;
283 unsigned long flags;
284
285 spin_lock_irqsave(&atimer->lock, flags);
286 list_del_init(&ti->running_list);
287 ti->need_update = 0;
288 spin_unlock_irqrestore(&atimer->lock, flags);
289 ct_xfitimer_update(atimer);
290}
291
292static void ct_xfitimer_free_global(struct ct_timer *atimer)
293{
294 ct_xfitimer_irq_stop(atimer);
295}
296
297static struct ct_timer_ops ct_xfitimer_ops = {
298 .prepare = ct_xfitimer_prepare,
299 .start = ct_xfitimer_start,
300 .stop = ct_xfitimer_stop,
301 .interrupt = ct_xfitimer_callback,
302 .free_global = ct_xfitimer_free_global,
303};
304
305/*
306 * timer instance
307 */
308
309struct ct_timer_instance *
310ct_timer_instance_new(struct ct_timer *atimer, struct ct_atc_pcm *apcm)
311{
312 struct ct_timer_instance *ti;
313
314 ti = kzalloc(sizeof(*ti), GFP_KERNEL);
315 if (!ti)
316 return NULL;
317 spin_lock_init(&ti->lock);
318 INIT_LIST_HEAD(&ti->instance_list);
319 INIT_LIST_HEAD(&ti->running_list);
320 ti->timer_base = atimer;
321 ti->apcm = apcm;
322 ti->substream = apcm->substream;
323 if (atimer->ops->init)
324 atimer->ops->init(ti);
325
326 spin_lock_irq(&atimer->list_lock);
327 list_add(&ti->instance_list, &atimer->instance_head);
328 spin_unlock_irq(&atimer->list_lock);
329
330 return ti;
331}
332
333void ct_timer_prepare(struct ct_timer_instance *ti)
334{
335 if (ti->timer_base->ops->prepare)
336 ti->timer_base->ops->prepare(ti);
337 ti->position = 0;
338 ti->running = 0;
339}
340
341void ct_timer_start(struct ct_timer_instance *ti)
342{
343 struct ct_timer *atimer = ti->timer_base;
344 atimer->ops->start(ti);
345}
346
347void ct_timer_stop(struct ct_timer_instance *ti)
348{
349 struct ct_timer *atimer = ti->timer_base;
350 atimer->ops->stop(ti);
351}
352
353void ct_timer_instance_free(struct ct_timer_instance *ti)
354{
355 struct ct_timer *atimer = ti->timer_base;
356
357 atimer->ops->stop(ti); /* to be sure */
358 if (atimer->ops->free_instance)
359 atimer->ops->free_instance(ti);
360
361 spin_lock_irq(&atimer->list_lock);
362 list_del(&ti->instance_list);
363 spin_unlock_irq(&atimer->list_lock);
364
365 kfree(ti);
366}
367
368/*
369 * timer manager
370 */
371
372#define USE_SYSTEM_TIMER 0
373
374static void ct_timer_interrupt(void *data, unsigned int status)
375{
376 struct ct_timer *timer = data;
377
378 /* Interval timer interrupt */
379 if ((status & IT_INT) && timer->ops->interrupt)
380 timer->ops->interrupt(timer);
381}
382
383struct ct_timer *ct_timer_new(struct ct_atc *atc)
384{
385 struct ct_timer *atimer;
386 struct hw *hw;
387
388 atimer = kzalloc(sizeof(*atimer), GFP_KERNEL);
389 if (!atimer)
390 return NULL;
391 spin_lock_init(&atimer->lock);
392 spin_lock_init(&atimer->list_lock);
393 INIT_LIST_HEAD(&atimer->instance_head);
394 INIT_LIST_HEAD(&atimer->running_head);
395 atimer->atc = atc;
396 hw = atc->hw;
397 if (!USE_SYSTEM_TIMER && hw->set_timer_irq) {
398 printk(KERN_INFO "ctxfi: Use xfi-native timer\n");
399 atimer->ops = &ct_xfitimer_ops;
400 hw->irq_callback_data = atimer;
401 hw->irq_callback = ct_timer_interrupt;
402 } else {
403 printk(KERN_INFO "ctxfi: Use system timer\n");
404 atimer->ops = &ct_systimer_ops;
405 }
406 return atimer;
407}
408
409void ct_timer_free(struct ct_timer *atimer)
410{
411 struct hw *hw = atimer->atc->hw;
412 hw->irq_callback = NULL;
413 if (atimer->ops->free_global)
414 atimer->ops->free_global(atimer);
415 kfree(atimer);
416}
417