aboutsummaryrefslogtreecommitdiffstats
path: root/sound/drivers/pcm-indirect2.c
diff options
context:
space:
mode:
Diffstat (limited to 'sound/drivers/pcm-indirect2.c')
-rw-r--r--sound/drivers/pcm-indirect2.c591
1 files changed, 591 insertions, 0 deletions
diff --git a/sound/drivers/pcm-indirect2.c b/sound/drivers/pcm-indirect2.c
new file mode 100644
index 000000000000..6a829cd03dde
--- /dev/null
+++ b/sound/drivers/pcm-indirect2.c
@@ -0,0 +1,591 @@
1/*
2 * Helper functions for indirect PCM data transfer to a simple FIFO in
3 * hardware (small, no possibility to read "hardware io position",
4 * updating position done by interrupt, ...)
5 *
6 * Copyright (c) by 2007 Joachim Foerster <JOFT@gmx.de>
7 *
8 * Based on "pcm-indirect.h" (alsa-driver-1.0.13) by
9 *
10 * Copyright (c) by Takashi Iwai <tiwai@suse.de>
11 * Jaroslav Kysela <perex@suse.cz>
12 *
13 * This program is free software; you can redistribute it and/or modify
14 * it under the terms of the GNU General Public License as published by
15 * the Free Software Foundation; either version 2 of the License, or
16 * (at your option) any later version.
17 *
18 * This program is distributed in the hope that it will be useful,
19 * but WITHOUT ANY WARRANTY; without even the implied warranty of
20 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 * GNU General Public License for more details.
22 *
23 * You should have received a copy of the GNU General Public License
24 * along with this program; if not, write to the Free Software
25 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
26 */
27
28/* #dependency of sound/core.h# */
29#include <sound/driver.h>
30/* snd_printk/d() */
31#include <sound/core.h>
32/* struct snd_pcm_substream, struct snd_pcm_runtime, snd_pcm_uframes_t
33 * snd_pcm_period_elapsed() */
34#include <sound/pcm.h>
35
36#include "pcm-indirect2.h"
37
38#ifdef SND_PCM_INDIRECT2_STAT
39/* jiffies */
40#include <linux/jiffies.h>
41
42void snd_pcm_indirect2_stat(struct snd_pcm_substream *substream,
43 struct snd_pcm_indirect2 *rec)
44{
45 struct snd_pcm_runtime *runtime = substream->runtime;
46 int i;
47 int j;
48 int k;
49 int seconds = (rec->lastbytetime - rec->firstbytetime) / HZ;
50
51 snd_printk(KERN_DEBUG "STAT: mul_elapsed: %u, mul_elapsed_real: %d, "
52 "irq_occured: %d\n",
53 rec->mul_elapsed, rec->mul_elapsed_real, rec->irq_occured);
54 snd_printk(KERN_DEBUG "STAT: min_multiple: %d (irqs/period)\n",
55 rec->min_multiple);
56 snd_printk(KERN_DEBUG "STAT: firstbytetime: %lu, lastbytetime: %lu, "
57 "firstzerotime: %lu\n",
58 rec->firstbytetime, rec->lastbytetime, rec->firstzerotime);
59 snd_printk(KERN_DEBUG "STAT: bytes2hw: %u Bytes => (by runtime->rate) "
60 "length: %d s\n",
61 rec->bytes2hw, rec->bytes2hw / 2 / 2 / runtime->rate);
62 snd_printk(KERN_DEBUG "STAT: (by measurement) length: %d => "
63 "rate: %d Bytes/s = %d Frames/s|Hz\n",
64 seconds, rec->bytes2hw / seconds,
65 rec->bytes2hw / 2 / 2 / seconds);
66 snd_printk(KERN_DEBUG
67 "STAT: zeros2hw: %u = %d ms ~ %d * %d zero copies\n",
68 rec->zeros2hw, ((rec->zeros2hw / 2 / 2) * 1000) /
69 runtime->rate,
70 rec->zeros2hw / (rec->hw_buffer_size / 2),
71 (rec->hw_buffer_size / 2));
72 snd_printk(KERN_DEBUG "STAT: pointer_calls: %u, lastdifftime: %u\n",
73 rec->pointer_calls, rec->lastdifftime);
74 snd_printk(KERN_DEBUG "STAT: sw_io: %d, sw_data: %d\n", rec->sw_io,
75 rec->sw_data);
76 snd_printk(KERN_DEBUG "STAT: byte_sizes[]:\n");
77 k = 0;
78 for (j = 0; j < 8; j++) {
79 for (i = j * 8; i < (j + 1) * 8; i++)
80 if (rec->byte_sizes[i] != 0) {
81 snd_printk(KERN_DEBUG "%u: %u",
82 i, rec->byte_sizes[i]);
83 k++;
84 }
85 if (((k % 8) == 0) && (k != 0)) {
86 snd_printk(KERN_DEBUG "\n");
87 k = 0;
88 }
89 }
90 snd_printk(KERN_DEBUG "\n");
91 snd_printk(KERN_DEBUG "STAT: zero_sizes[]:\n");
92 for (j = 0; j < 8; j++) {
93 k = 0;
94 for (i = j * 8; i < (j + 1) * 8; i++)
95 if (rec->zero_sizes[i] != 0)
96 snd_printk(KERN_DEBUG "%u: %u",
97 i, rec->zero_sizes[i]);
98 else
99 k++;
100 if (!k)
101 snd_printk(KERN_DEBUG "\n");
102 }
103 snd_printk(KERN_DEBUG "\n");
104 snd_printk(KERN_DEBUG "STAT: min_adds[]:\n");
105 for (j = 0; j < 8; j++) {
106 if (rec->min_adds[j] != 0)
107 snd_printk(KERN_DEBUG "%u: %u", j, rec->min_adds[j]);
108 }
109 snd_printk(KERN_DEBUG "\n");
110 snd_printk(KERN_DEBUG "STAT: mul_adds[]:\n");
111 for (j = 0; j < 8; j++) {
112 if (rec->mul_adds[j] != 0)
113 snd_printk(KERN_DEBUG "%u: %u", j, rec->mul_adds[j]);
114 }
115 snd_printk(KERN_DEBUG "\n");
116 snd_printk(KERN_DEBUG
117 "STAT: zero_times_saved: %d, zero_times_notsaved: %d\n",
118 rec->zero_times_saved, rec->zero_times_notsaved);
119 /* snd_printk(KERN_DEBUG "STAT: zero_times[]\n");
120 i = 0;
121 for (j = 0; j < 3750; j++) {
122 if (rec->zero_times[j] != 0) {
123 snd_printk(KERN_DEBUG "%u: %u", j, rec->zero_times[j]);
124 i++;
125 }
126 if (((i % 8) == 0) && (i != 0))
127 snd_printk(KERN_DEBUG "\n");
128 }
129 snd_printk(KERN_DEBUG "\n"); */
130 return;
131}
132#endif
133
134/*
135 * _internal_ helper function for playback/capture transfer function
136 */
137static void
138snd_pcm_indirect2_increase_min_periods(struct snd_pcm_substream *substream,
139 struct snd_pcm_indirect2 *rec,
140 int isplay, int iscopy,
141 unsigned int bytes)
142{
143 if (rec->min_periods >= 0) {
144 if (iscopy) {
145 rec->sw_io += bytes;
146 if (rec->sw_io >= rec->sw_buffer_size)
147 rec->sw_io -= rec->sw_buffer_size;
148 } else if (isplay) {
149 /* If application does not write data in multiples of
150 * a period, move sw_data to the next correctly aligned
151 * position, so that sw_io can converge to it (in the
152 * next step).
153 */
154 if (!rec->check_alignment) {
155 if (rec->bytes2hw %
156 snd_pcm_lib_period_bytes(substream)) {
157 unsigned bytes2hw_aligned =
158 (1 +
159 (rec->bytes2hw /
160 snd_pcm_lib_period_bytes
161 (substream))) *
162 snd_pcm_lib_period_bytes
163 (substream);
164 rec->sw_data =
165 bytes2hw_aligned %
166 rec->sw_buffer_size;
167#ifdef SND_PCM_INDIRECT2_STAT
168 snd_printk(KERN_DEBUG
169 "STAT: @re-align: aligned "
170 "bytes2hw to next period "
171 "size boundary: %d "
172 "(instead of %d)\n",
173 bytes2hw_aligned,
174 rec->bytes2hw);
175 snd_printk(KERN_DEBUG
176 "STAT: @re-align: sw_data "
177 "moves to: %d\n",
178 rec->sw_data);
179#endif
180 }
181 rec->check_alignment = 1;
182 }
183 /* We are at the end and are copying zeros into the
184 * fifo.
185 * Now, we have to make sure that sw_io is increased
186 * until the position of sw_data: Filling the fifo with
187 * the first zeros means, the last bytes were played.
188 */
189 if (rec->sw_io != rec->sw_data) {
190 unsigned int diff;
191 if (rec->sw_data > rec->sw_io)
192 diff = rec->sw_data - rec->sw_io;
193 else
194 diff = (rec->sw_buffer_size -
195 rec->sw_io) +
196 rec->sw_data;
197 if (bytes >= diff)
198 rec->sw_io = rec->sw_data;
199 else {
200 rec->sw_io += bytes;
201 if (rec->sw_io >= rec->sw_buffer_size)
202 rec->sw_io -=
203 rec->sw_buffer_size;
204 }
205 }
206 }
207 rec->min_period_count += bytes;
208 if (rec->min_period_count >= (rec->hw_buffer_size / 2)) {
209 rec->min_periods += (rec->min_period_count /
210 (rec->hw_buffer_size / 2));
211#ifdef SND_PCM_INDIRECT2_STAT
212 if ((rec->min_period_count /
213 (rec->hw_buffer_size / 2)) > 7)
214 snd_printk(KERN_DEBUG
215 "STAT: more than 7 (%d) min_adds "
216 "at once - too big to save!\n",
217 (rec->min_period_count /
218 (rec->hw_buffer_size / 2)));
219 else
220 rec->min_adds[(rec->min_period_count /
221 (rec->hw_buffer_size / 2))]++;
222#endif
223 rec->min_period_count = (rec->min_period_count %
224 (rec->hw_buffer_size / 2));
225 }
226 } else if (isplay && iscopy)
227 rec->min_periods = 0;
228}
229
230/*
231 * helper function for playback/capture pointer callback
232 */
233snd_pcm_uframes_t
234snd_pcm_indirect2_pointer(struct snd_pcm_substream *substream,
235 struct snd_pcm_indirect2 *rec)
236{
237#ifdef SND_PCM_INDIRECT2_STAT
238 rec->pointer_calls++;
239#endif
240 return bytes_to_frames(substream->runtime, rec->sw_io);
241}
242
243/*
244 * _internal_ helper function for playback interrupt callback
245 */
246static void
247snd_pcm_indirect2_playback_transfer(struct snd_pcm_substream *substream,
248 struct snd_pcm_indirect2 *rec,
249 snd_pcm_indirect2_copy_t copy,
250 snd_pcm_indirect2_zero_t zero)
251{
252 struct snd_pcm_runtime *runtime = substream->runtime;
253 snd_pcm_uframes_t appl_ptr = runtime->control->appl_ptr;
254
255 /* runtime->control->appl_ptr: position where ALSA will write next time
256 * rec->appl_ptr: position where ALSA was last time
257 * diff: obviously ALSA wrote that much bytes into the intermediate
258 * buffer since we checked last time
259 */
260 snd_pcm_sframes_t diff = appl_ptr - rec->appl_ptr;
261
262 if (diff) {
263#ifdef SND_PCM_INDIRECT2_STAT
264 rec->lastdifftime = jiffies;
265#endif
266 if (diff < -(snd_pcm_sframes_t) (runtime->boundary / 2))
267 diff += runtime->boundary;
268 /* number of bytes "added" by ALSA increases the number of
269 * bytes which are ready to "be transfered to HW"/"played"
270 * Then, set rec->appl_ptr to not count bytes twice next time.
271 */
272 rec->sw_ready += (int)frames_to_bytes(runtime, diff);
273 rec->appl_ptr = appl_ptr;
274 }
275 if (rec->hw_ready && (rec->sw_ready <= 0)) {
276 unsigned int bytes;
277
278#ifdef SND_PCM_INDIRECT2_STAT
279 if (rec->firstzerotime == 0) {
280 rec->firstzerotime = jiffies;
281 snd_printk(KERN_DEBUG
282 "STAT: @firstzerotime: mul_elapsed: %d, "
283 "min_period_count: %d\n",
284 rec->mul_elapsed, rec->min_period_count);
285 snd_printk(KERN_DEBUG
286 "STAT: @firstzerotime: sw_io: %d, "
287 "sw_data: %d, appl_ptr: %u\n",
288 rec->sw_io, rec->sw_data,
289 (unsigned int)appl_ptr);
290 }
291 if ((jiffies - rec->firstzerotime) < 3750) {
292 rec->zero_times[(jiffies - rec->firstzerotime)]++;
293 rec->zero_times_saved++;
294 } else
295 rec->zero_times_notsaved++;
296#endif
297 bytes = zero(substream, rec);
298
299#ifdef SND_PCM_INDIRECT2_STAT
300 rec->zeros2hw += bytes;
301 if (bytes < 64)
302 rec->zero_sizes[bytes]++;
303 else
304 snd_printk(KERN_DEBUG
305 "STAT: %d zero Bytes copied to hardware at "
306 "once - too big to save!\n",
307 bytes);
308#endif
309 snd_pcm_indirect2_increase_min_periods(substream, rec, 1, 0,
310 bytes);
311 return;
312 }
313 while (rec->hw_ready && (rec->sw_ready > 0)) {
314 /* sw_to_end: max. number of bytes that can be read/take from
315 * the current position (sw_data) in _one_ step
316 */
317 unsigned int sw_to_end = rec->sw_buffer_size - rec->sw_data;
318
319 /* bytes: number of bytes we have available (for reading) */
320 unsigned int bytes = rec->sw_ready;
321
322 if (sw_to_end < bytes)
323 bytes = sw_to_end;
324 if (!bytes)
325 break;
326
327#ifdef SND_PCM_INDIRECT2_STAT
328 if (rec->firstbytetime == 0)
329 rec->firstbytetime = jiffies;
330 rec->lastbytetime = jiffies;
331#endif
332 /* copy bytes from intermediate buffer position sw_data to the
333 * HW and return number of bytes actually written
334 * Furthermore, set hw_ready to 0, if the fifo isn't empty
335 * now => more could be transfered to fifo
336 */
337 bytes = copy(substream, rec, bytes);
338 rec->bytes2hw += bytes;
339
340#ifdef SND_PCM_INDIRECT2_STAT
341 if (bytes < 64)
342 rec->byte_sizes[bytes]++;
343 else
344 snd_printk(KERN_DEBUG
345 "STAT: %d Bytes copied to hardware at once "
346 "- too big to save!\n",
347 bytes);
348#endif
349 /* increase sw_data by the number of actually written bytes
350 * (= number of taken bytes from intermediate buffer)
351 */
352 rec->sw_data += bytes;
353 if (rec->sw_data == rec->sw_buffer_size)
354 rec->sw_data = 0;
355 /* now sw_data is the position where ALSA is going to write
356 * in the intermediate buffer next time = position we are going
357 * to read from next time
358 */
359
360 snd_pcm_indirect2_increase_min_periods(substream, rec, 1, 1,
361 bytes);
362
363 /* we read bytes from intermediate buffer, so we need to say
364 * that the number of bytes ready for transfer are decreased
365 * now
366 */
367 rec->sw_ready -= bytes;
368 }
369 return;
370}
371
372/*
373 * helper function for playback interrupt routine
374 */
375void
376snd_pcm_indirect2_playback_interrupt(struct snd_pcm_substream *substream,
377 struct snd_pcm_indirect2 *rec,
378 snd_pcm_indirect2_copy_t copy,
379 snd_pcm_indirect2_zero_t zero)
380{
381#ifdef SND_PCM_INDIRECT2_STAT
382 rec->irq_occured++;
383#endif
384 /* hardware played some bytes, so there is room again (in fifo) */
385 rec->hw_ready = 1;
386
387 /* don't call ack() now, instead call transfer() function directly
388 * (normally called by ack() )
389 */
390 snd_pcm_indirect2_playback_transfer(substream, rec, copy, zero);
391
392 if (rec->min_periods >= rec->min_multiple) {
393#ifdef SND_PCM_INDIRECT2_STAT
394 if ((rec->min_periods / rec->min_multiple) > 7)
395 snd_printk(KERN_DEBUG
396 "STAT: more than 7 (%d) mul_adds - too big "
397 "to save!\n",
398 (rec->min_periods / rec->min_multiple));
399 else
400 rec->mul_adds[(rec->min_periods /
401 rec->min_multiple)]++;
402 rec->mul_elapsed_real += (rec->min_periods /
403 rec->min_multiple);
404 rec->mul_elapsed++;
405#endif
406 rec->min_periods = 0;
407 snd_pcm_period_elapsed(substream);
408 }
409}
410
411/*
412 * _internal_ helper function for capture interrupt callback
413 */
414static void
415snd_pcm_indirect2_capture_transfer(struct snd_pcm_substream *substream,
416 struct snd_pcm_indirect2 *rec,
417 snd_pcm_indirect2_copy_t copy,
418 snd_pcm_indirect2_zero_t null)
419{
420 struct snd_pcm_runtime *runtime = substream->runtime;
421 snd_pcm_uframes_t appl_ptr = runtime->control->appl_ptr;
422 snd_pcm_sframes_t diff = appl_ptr - rec->appl_ptr;
423
424 if (diff) {
425#ifdef SND_PCM_INDIRECT2_STAT
426 rec->lastdifftime = jiffies;
427#endif
428 if (diff < -(snd_pcm_sframes_t) (runtime->boundary / 2))
429 diff += runtime->boundary;
430 rec->sw_ready -= frames_to_bytes(runtime, diff);
431 rec->appl_ptr = appl_ptr;
432 }
433 /* if hardware has something, but the intermediate buffer is full
434 * => skip contents of buffer
435 */
436 if (rec->hw_ready && (rec->sw_ready >= (int)rec->sw_buffer_size)) {
437 unsigned int bytes;
438
439#ifdef SND_PCM_INDIRECT2_STAT
440 if (rec->firstzerotime == 0) {
441 rec->firstzerotime = jiffies;
442 snd_printk(KERN_DEBUG "STAT: (capture) "
443 "@firstzerotime: mul_elapsed: %d, "
444 "min_period_count: %d\n",
445 rec->mul_elapsed, rec->min_period_count);
446 snd_printk(KERN_DEBUG "STAT: (capture) "
447 "@firstzerotime: sw_io: %d, sw_data: %d, "
448 "appl_ptr: %u\n",
449 rec->sw_io, rec->sw_data,
450 (unsigned int)appl_ptr);
451 }
452 if ((jiffies - rec->firstzerotime) < 3750) {
453 rec->zero_times[(jiffies - rec->firstzerotime)]++;
454 rec->zero_times_saved++;
455 } else
456 rec->zero_times_notsaved++;
457#endif
458 bytes = null(substream, rec);
459
460#ifdef SND_PCM_INDIRECT2_STAT
461 rec->zeros2hw += bytes;
462 if (bytes < 64)
463 rec->zero_sizes[bytes]++;
464 else
465 snd_printk(KERN_DEBUG
466 "STAT: (capture) %d zero Bytes copied to "
467 "hardware at once - too big to save!\n",
468 bytes);
469#endif
470 snd_pcm_indirect2_increase_min_periods(substream, rec, 0, 0,
471 bytes);
472 /* report an overrun */
473 rec->sw_io = SNDRV_PCM_POS_XRUN;
474 return;
475 }
476 while (rec->hw_ready && (rec->sw_ready < (int)rec->sw_buffer_size)) {
477 /* sw_to_end: max. number of bytes that we can write to the
478 * intermediate buffer (until it's end)
479 */
480 size_t sw_to_end = rec->sw_buffer_size - rec->sw_data;
481
482 /* bytes: max. number of bytes, which may be copied to the
483 * intermediate buffer without overflow (in _one_ step)
484 */
485 size_t bytes = rec->sw_buffer_size - rec->sw_ready;
486
487 /* limit number of bytes (for transfer) by available room in
488 * the intermediate buffer
489 */
490 if (sw_to_end < bytes)
491 bytes = sw_to_end;
492 if (!bytes)
493 break;
494
495#ifdef SND_PCM_INDIRECT2_STAT
496 if (rec->firstbytetime == 0)
497 rec->firstbytetime = jiffies;
498 rec->lastbytetime = jiffies;
499#endif
500 /* copy bytes from the intermediate buffer (position sw_data)
501 * to the HW at most and return number of bytes actually copied
502 * from HW
503 * Furthermore, set hw_ready to 0, if the fifo is empty now.
504 */
505 bytes = copy(substream, rec, bytes);
506 rec->bytes2hw += bytes;
507
508#ifdef SND_PCM_INDIRECT2_STAT
509 if (bytes < 64)
510 rec->byte_sizes[bytes]++;
511 else
512 snd_printk(KERN_DEBUG
513 "STAT: (capture) %d Bytes copied to "
514 "hardware at once - too big to save!\n",
515 bytes);
516#endif
517 /* increase sw_data by the number of actually copied bytes from
518 * HW
519 */
520 rec->sw_data += bytes;
521 if (rec->sw_data == rec->sw_buffer_size)
522 rec->sw_data = 0;
523
524 snd_pcm_indirect2_increase_min_periods(substream, rec, 0, 1,
525 bytes);
526
527 /* number of bytes in the intermediate buffer, which haven't
528 * been fetched by ALSA yet.
529 */
530 rec->sw_ready += bytes;
531 }
532 return;
533}
534
535/*
536 * helper function for capture interrupt routine
537 */
538void
539snd_pcm_indirect2_capture_interrupt(struct snd_pcm_substream *substream,
540 struct snd_pcm_indirect2 *rec,
541 snd_pcm_indirect2_copy_t copy,
542 snd_pcm_indirect2_zero_t null)
543{
544#ifdef SND_PCM_INDIRECT2_STAT
545 rec->irq_occured++;
546#endif
547 /* hardware recorded some bytes, so there is something to read from the
548 * record fifo:
549 */
550 rec->hw_ready = 1;
551
552 /* don't call ack() now, instead call transfer() function directly
553 * (normally called by ack() )
554 */
555 snd_pcm_indirect2_capture_transfer(substream, rec, copy, null);
556
557 if (rec->min_periods >= rec->min_multiple) {
558
559#ifdef SND_PCM_INDIRECT2_STAT
560 if ((rec->min_periods / rec->min_multiple) > 7)
561 snd_printk(KERN_DEBUG
562 "STAT: more than 7 (%d) mul_adds - "
563 "too big to save!\n",
564 (rec->min_periods / rec->min_multiple));
565 else
566 rec->mul_adds[(rec->min_periods /
567 rec->min_multiple)]++;
568 rec->mul_elapsed_real += (rec->min_periods /
569 rec->min_multiple);
570 rec->mul_elapsed++;
571
572 if (!(rec->mul_elapsed % 4)) {
573 struct snd_pcm_runtime *runtime = substream->runtime;
574 unsigned int appl_ptr =
575 frames_to_bytes(runtime,
576 (unsigned int)runtime->control->
577 appl_ptr) % rec->sw_buffer_size;
578 int diff = rec->sw_data - appl_ptr;
579 if (diff < 0)
580 diff += rec->sw_buffer_size;
581 snd_printk(KERN_DEBUG
582 "STAT: mul_elapsed: %d, sw_data: %u, "
583 "appl_ptr (bytes): %u, diff: %d\n",
584 rec->mul_elapsed, rec->sw_data, appl_ptr,
585 diff);
586 }
587#endif
588 rec->min_periods = 0;
589 snd_pcm_period_elapsed(substream);
590 }
591}