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