diff options
Diffstat (limited to 'sound/pci/ctxfi/cttimer.c')
-rw-r--r-- | sound/pci/ctxfi/cttimer.c | 417 |
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 | |||
16 | struct 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 */ | ||
27 | struct 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 */ | ||
42 | struct 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 | |||
59 | static 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 | |||
87 | static void ct_systimer_init(struct ct_timer_instance *ti) | ||
88 | { | ||
89 | setup_timer(&ti->timer, ct_systimer_callback, | ||
90 | (unsigned long)ti); | ||
91 | } | ||
92 | |||
93 | static 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 | |||
106 | static 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 | |||
116 | static 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 | |||
124 | static 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 | |||
140 | static 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 | |||
151 | static 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 | */ | ||
169 | static 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 */ | ||
208 | static 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 */ | ||
224 | static 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 | |||
242 | static 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 */ | ||
250 | static 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 | |||
269 | static 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 | |||
280 | static 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 | |||
292 | static void ct_xfitimer_free_global(struct ct_timer *atimer) | ||
293 | { | ||
294 | ct_xfitimer_irq_stop(atimer); | ||
295 | } | ||
296 | |||
297 | static 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 | |||
309 | struct ct_timer_instance * | ||
310 | ct_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 | |||
333 | void 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 | |||
341 | void ct_timer_start(struct ct_timer_instance *ti) | ||
342 | { | ||
343 | struct ct_timer *atimer = ti->timer_base; | ||
344 | atimer->ops->start(ti); | ||
345 | } | ||
346 | |||
347 | void ct_timer_stop(struct ct_timer_instance *ti) | ||
348 | { | ||
349 | struct ct_timer *atimer = ti->timer_base; | ||
350 | atimer->ops->stop(ti); | ||
351 | } | ||
352 | |||
353 | void 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 | |||
374 | static 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 | |||
383 | struct 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 | |||
409 | void 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 | |||